Loading...
Loading...
Compare original and translation side by side
mastermaingit checkout -b feat/virality-$(date +%Y%m%d)mastermaingit checkout -b feat/virality-$(date +%Y%m%d)undefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefinedundefined// app/layout.tsx
import type { Metadata } from 'next';
export const metadata: Metadata = {
metadataBase: new URL(process.env.NEXT_PUBLIC_APP_URL!),
title: {
default: 'Your Product - Tagline',
template: '%s | Your Product',
},
description: 'One sentence that makes people want to try it.',
openGraph: {
type: 'website',
locale: 'en_US',
siteName: 'Your Product',
images: ['/og-default.png'],
},
twitter: {
card: 'summary_large_image',
creator: '@yourhandle',
},
};// app/layout.tsx
import type { Metadata } from 'next';
export const metadata: Metadata = {
metadataBase: new URL(process.env.NEXT_PUBLIC_APP_URL!),
title: {
default: 'Your Product - Tagline',
template: '%s | Your Product',
},
description: 'One sentence that makes people want to try it.',
openGraph: {
type: 'website',
locale: 'en_US',
siteName: 'Your Product',
images: ['/og-default.png'],
},
twitter: {
card: 'summary_large_image',
creator: '@yourhandle',
},
};// app/api/og/route.tsx
import { ImageResponse } from 'next/og';
export const runtime = 'edge';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const title = searchParams.get('title') ?? 'Your Product';
const description = searchParams.get('description') ?? '';
return new ImageResponse(
(
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#000',
color: '#fff',
fontFamily: 'system-ui',
}}
>
<div style={{ fontSize: 60, fontWeight: 'bold' }}>{title}</div>
{description && (
<div style={{ fontSize: 30, marginTop: 20, opacity: 0.8 }}>
{description}
</div>
)}
</div>
),
{ width: 1200, height: 630 }
);
}// app/api/og/route.tsx
import { ImageResponse } from 'next/og';
export const runtime = 'edge';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const title = searchParams.get('title') ?? 'Your Product';
const description = searchParams.get('description') ?? '';
return new ImageResponse(
(
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#000',
color: '#fff',
fontFamily: 'system-ui',
}}
>
<div style={{ fontSize: 60, fontWeight: 'bold' }}>{title}</div>
{description && (
<div style={{ fontSize: 30, marginTop: 20, opacity: 0.8 }}>
{description}
</div>
)}
</div>
),
{ width: 1200, height: 630 }
);
}// app/[slug]/page.tsx
import type { Metadata } from 'next';
export async function generateMetadata({ params }): Promise<Metadata> {
const item = await getItem(params.slug);
return {
title: item.title,
description: item.description,
openGraph: {
title: item.title,
description: item.description,
images: [`/api/og?title=${encodeURIComponent(item.title)}`],
},
};
}// app/[slug]/page.tsx
import type { Metadata } from 'next';
export async function generateMetadata({ params }): Promise<Metadata> {
const item = await getItem(params.slug);
return {
title: item.title,
description: item.description,
openGraph: {
title: item.title,
description: item.description,
images: [`/api/og?title=${encodeURIComponent(item.title)}`],
},
};
}// components/share-button.tsx
'use client';
import { useState } from 'react';
interface ShareButtonProps {
url: string;
title: string;
text?: string;
}
export function ShareButton({ url, title, text }: ShareButtonProps) {
const [copied, setCopied] = useState(false);
const share = async () => {
// Try native share first (mobile)
if (navigator.share) {
try {
await navigator.share({ url, title, text });
return;
} catch {
// User cancelled or not supported
}
}
// Fallback to clipboard
await navigator.clipboard.writeText(url);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<button onClick={share}>
{copied ? 'Copied!' : 'Share'}
</button>
);
}// components/share-button.tsx
'use client';
import { useState } from 'react';
interface ShareButtonProps {
url: string;
title: string;
text?: string;
}
export function ShareButton({ url, title, text }: ShareButtonProps) {
const [copied, setCopied] = useState(false);
const share = async () => {
// Try native share first (mobile)
if (navigator.share) {
try {
await navigator.share({ url, title, text });
return;
} catch {
// User cancelled or not supported
}
}
// Fallback to clipboard
await navigator.clipboard.writeText(url);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<button onClick={share}>
{copied ? 'Copied!' : 'Share'}
</button>
);
}// After user completes an action
function onComplete() {
// Show share prompt
showShareModal({
title: "You did the thing!",
prompt: "Share your achievement?",
url: `${baseUrl}/share/${resultId}`,
prefilledText: "I just [did thing] with @YourProduct!",
});
}// After user completes an action
function onComplete() {
// Show share prompt
showShareModal({
title: "You did the thing!",
prompt: "Share your achievement?",
url: `${baseUrl}/share/${resultId}`,
prefilledText: "I just [did thing] with @YourProduct!",
});
}// lib/referrals.ts
export function generateReferralCode(userId: string): string {
// Short, memorable codes
return `${userId.slice(0, 4).toUpperCase()}${randomChars(4)}`;
}
export function getReferralUrl(code: string): string {
return `${process.env.NEXT_PUBLIC_APP_URL}?ref=${code}`;
}
// Track on signup
export async function trackReferral(newUserId: string, refCode: string | null) {
if (!refCode) return;
const referrer = await getUserByRefCode(refCode);
if (!referrer) return;
await db.referrals.create({
referrerId: referrer.id,
referredId: newUserId,
code: refCode,
createdAt: new Date(),
});
// Reward referrer (if applicable)
await grantReferralReward(referrer.id);
}// lib/referrals.ts
export function generateReferralCode(userId: string): string {
// Short, memorable codes
return `${userId.slice(0, 4).toUpperCase()}${randomChars(4)}`;
}
export function getReferralUrl(code: string): string {
return `${process.env.NEXT_PUBLIC_APP_URL}?ref=${code}`;
}
// Track on signup
export async function trackReferral(newUserId: string, refCode: string | null) {
if (!refCode) return;
const referrer = await getUserByRefCode(refCode);
if (!referrer) return;
await db.referrals.create({
referrerId: referrer.id,
referredId: newUserId,
code: refCode,
createdAt: new Date(),
});
// Reward referrer (if applicable)
await grantReferralReward(referrer.id);
}// middleware.ts or on page load
export function captureAttribution() {
if (typeof window === 'undefined') return;
const params = new URLSearchParams(window.location.search);
const attribution = {
utm_source: params.get('utm_source'),
utm_medium: params.get('utm_medium'),
utm_campaign: params.get('utm_campaign'),
ref: params.get('ref'),
referrer: document.referrer,
};
// Store for later (signup, conversion)
sessionStorage.setItem('attribution', JSON.stringify(attribution));
}// middleware.ts or on page load
export function captureAttribution() {
if (typeof window === 'undefined') return;
const params = new URLSearchParams(window.location.search);
const attribution = {
utm_source: params.get('utm_source'),
utm_medium: params.get('utm_medium'),
utm_campaign: params.get('utm_campaign'),
ref: params.get('ref'),
referrer: document.referrer,
};
// Store for later (signup, conversion)
sessionStorage.setItem('attribution', JSON.stringify(attribution));
}// K-factor = invites sent per user × conversion rate
// K > 1 means viral growth
export async function calculateKFactor(timeWindow: string = '30d') {
const activeUsers = await countActiveUsers(timeWindow);
const invitesSent = await countInvitesSent(timeWindow);
const inviteConversions = await countInviteConversions(timeWindow);
const invitesPerUser = invitesSent / activeUsers;
const conversionRate = inviteConversions / invitesSent;
const kFactor = invitesPerUser * conversionRate;
return {
kFactor,
invitesPerUser,
conversionRate,
isViral: kFactor > 1,
};
}// K-factor = invites sent per user × conversion rate
// K > 1 means viral growth
export async function calculateKFactor(timeWindow: string = '30d') {
const activeUsers = await countActiveUsers(timeWindow);
const invitesSent = await countInvitesSent(timeWindow);
const inviteConversions = await countInviteConversions(timeWindow);
const invitesPerUser = invitesSent / activeUsers;
const conversionRate = inviteConversions / invitesSent;
const kFactor = invitesPerUser * conversionRate;
return {
kFactor,
invitesPerUser,
conversionRate,
isViral: kFactor > 1,
};
}undefinedundefined
**Test dynamic OG images:**
```bash
**测试动态OG图片:**
```bash
**Test share flow:**
1. Create/complete something in the app
2. Click share button
3. Verify URL is correct and copyable
4. Paste URL in Twitter/LinkedIn preview checker
5. Verify preview looks good
**Test referral flow:**
1. Get your referral link
2. Open in incognito
3. Sign up
4. Verify referral tracked
5. Verify referrer credited
**Test mobile share:**
1. Open on mobile device
2. Click share
3. Verify native share sheet appears
4. Complete share to an app
If any verification fails, go back and fix it.
**测试分享流程:**
1. 在应用内创建/完成某项操作
2. 点击分享按钮
3. 验证URL正确且可复制
4. 将URL粘贴到Twitter/LinkedIn预览工具中
5. 确认预览效果符合预期
**测试推荐流程:**
1. 获取你的推荐链接
2. 在隐身模式下打开
3. 完成注册
4. 验证推荐关系已被追踪
5. 验证推荐人已获得奖励
**测试移动端分享:**
1. 在移动设备上打开应用
2. 点击分享按钮
3. 确认原生分享面板弹出
4. 完成分享到某款应用
如果任何验证步骤失败,返回修改后重新验证。User creates something → Prompted to share → Friend sees → Creates their own → ...用户创建内容 → 触发分享提示 → 好友看到 → 创建自己的内容 → ...User hits milestone → Shareable achievement card → Social proof → Friend tries → ...用户达成里程碑 → 生成可分享的成就卡片 → 形成社交证明 → 好友尝试使用 → ...User invites friend → Friend joins → Both rewarded → Friend invites → ...用户邀请好友 → 好友加入 → 双方获得奖励 → 好友继续邀请 → ...User creates content → Content has product watermark → Viewer sees → Visits product → ...用户创建内容 → 内容带有产品水印 → 浏览者看到 → 访问产品 → ...undefinedundefinedundefinedundefinedlaunch-strategysocial-contentmarketing-ideaslaunch-strategysocial-contentmarketing-ideas