ssr-ssg-advisor
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSSR/SSG Advisor
SSR/SSG 渲染策略选型指南
Choose the optimal rendering strategy for Next.js pages based on requirements.
根据需求为Next.js页面选择最优的渲染策略。
Quick Start
快速入门
Decision criteria:
- SSG: Static content, pre-render at build → Best performance
- ISR: Static with updates, revalidate periodically → Balance of both
- SSR: Dynamic per-request, personalized → Fresh data
- CSR: Client-side only, highly interactive → User-specific
决策标准:
- SSG:静态内容,构建时预渲染 → 性能最优
- ISR:支持更新的静态内容,定期重新验证 → 兼顾性能与新鲜度
- SSR:每次请求动态渲染,支持个性化 → 数据实时新鲜
- CSR:仅客户端渲染,高交互性 → 适配用户专属场景
Instructions
操作步骤
Step 1: Analyze Content Requirements
步骤1:分析内容需求
Ask these questions:
- Does content change per user? (personalization)
- How frequently does content update?
- Is SEO critical?
- What's the acceptable data freshness?
- How many pages need to be generated?
思考以下问题:
- 内容是否因用户而异?(个性化需求)
- 内容更新频率如何?
- SEO是否关键?
- 可接受的数据新鲜度是多少?
- 需要生成多少页面?
Step 2: Choose Rendering Strategy
步骤2:选择渲染策略
Static Site Generation (SSG):
typescript
// pages/products/[id].tsx
export async function getStaticProps({ params }) {
const product = await fetchProduct(params.id);
return {
props: { product },
// Optional: revalidate for ISR
// revalidate: 60, // seconds
};
}
export async function getStaticPaths() {
const products = await fetchAllProducts();
return {
paths: products.map(p => ({ params: { id: p.id } })),
fallback: 'blocking', // or false, or true
};
}When to use SSG:
- Marketing pages
- Blog posts
- Documentation
- Product catalogs (if manageable size)
- Any content that doesn't change often
Server-Side Rendering (SSR):
typescript
// pages/dashboard.tsx
export async function getServerSideProps(context) {
const session = await getSession(context);
const data = await fetchUserData(session.userId);
return {
props: { data },
};
}When to use SSR:
- User dashboards
- Personalized content
- Real-time data
- Content requiring authentication
- Frequently changing data
Incremental Static Regeneration (ISR):
typescript
// pages/blog/[slug].tsx
export async function getStaticProps({ params }) {
const post = await fetchPost(params.slug);
return {
props: { post },
revalidate: 60, // Regenerate every 60 seconds
};
}When to use ISR:
- Blog with frequent updates
- Product pages with price changes
- News sites
- Content that updates periodically
- Large sites where full rebuild is slow
Client-Side Rendering (CSR):
typescript
'use client'; // App Router
import { useEffect, useState } from 'react';
function Dashboard() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/user-data')
.then(res => res.json())
.then(setData);
}, []);
return <div>{data ? <Content data={data} /> : <Loading />}</div>;
}When to use CSR:
- Highly interactive UIs
- User-specific data (after auth)
- Real-time updates
- SEO not required
- Data behind authentication
静态站点生成(SSG):
typescript
// pages/products/[id].tsx
export async function getStaticProps({ params }) {
const product = await fetchProduct(params.id);
return {
props: { product },
// 可选:启用ISR重新验证
// revalidate: 60, // 秒
};
}
export async function getStaticPaths() {
const products = await fetchAllProducts();
return {
paths: products.map(p => ({ params: { id: p.id } })),
fallback: 'blocking', // 或 false、true
};
}SSG适用场景:
- 营销页面
- 博客文章
- 文档
- 产品目录(若规模可控)
- 不常更新的内容
服务端渲染(SSR):
typescript
// pages/dashboard.tsx
export async function getServerSideProps(context) {
const session = await getSession(context);
const data = await fetchUserData(session.userId);
return {
props: { data },
};
}SSR适用场景:
- 用户仪表盘
- 个性化内容
- 实时数据
- 需要身份验证的内容
- 频繁更新的数据
增量静态再生(ISR):
typescript
// pages/blog/[slug].tsx
export async function getStaticProps({ params }) {
const post = await fetchPost(params.slug);
return {
props: { post },
revalidate: 60, // 每60秒重新生成
};
}ISR适用场景:
- 频繁更新的博客
- 价格变动的产品页面
- 新闻站点
- 定期更新的内容
- 全量重建缓慢的大型站点
客户端渲染(CSR):
typescript
'use client'; // App Router
import { useEffect, useState } from 'react';
function Dashboard() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/user-data')
.then(res => res.json())
.then(setData);
}, []);
return <div>{data ? <Content data={data} /> : <Loading />}</div>;
}CSR适用场景:
- 高交互性UI
- 用户专属数据(认证后)
- 实时更新
- 无需SEO
- 身份验证后方可访问的数据
Step 3: Implement Data Fetching
步骤3:实现数据获取
App Router (Next.js 13+):
typescript
// app/products/page.tsx
async function ProductsPage() {
// SSG: cached by default
const products = await fetch('https://api.example.com/products');
// ISR: revalidate periodically
const products = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 } // 1 hour
});
// SSR: no caching
const products = await fetch('https://api.example.com/products', {
cache: 'no-store'
});
return <ProductList products={products} />;
}Pages Router:
typescript
// getStaticProps: SSG/ISR
// getServerSideProps: SSR
// useEffect + fetch: CSRApp Router(Next.js 13+):
typescript
// app/products/page.tsx
async function ProductsPage() {
// SSG:默认缓存
const products = await fetch('https://api.example.com/products');
// ISR:定期重新验证
const products = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 } // 1小时
});
// SSR:无缓存
const products = await fetch('https://api.example.com/products', {
cache: 'no-store'
});
return <ProductList products={products} />;
}Pages Router:
typescript
// getStaticProps: SSG/ISR
// getServerSideProps: SSR
// useEffect + fetch: CSRStep 4: Configure Caching and Revalidation
步骤4:配置缓存与重新验证
On-demand revalidation:
typescript
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';
export async function POST(request) {
const path = request.nextUrl.searchParams.get('path');
if (path) {
revalidatePath(path);
return Response.json({ revalidated: true });
}
return Response.json({ revalidated: false });
}Tagged caching:
typescript
// Fetch with tags
const data = await fetch('https://api.example.com/products', {
next: { tags: ['products'] }
});
// Revalidate by tag
revalidateTag('products');按需重新验证:
typescript
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';
export async function POST(request) {
const path = request.nextUrl.searchParams.get('path');
if (path) {
revalidatePath(path);
return Response.json({ revalidated: true });
}
return Response.json({ revalidated: false });
}标签化缓存:
typescript
// 带标签的请求
const data = await fetch('https://api.example.com/products', {
next: { tags: ['products'] }
});
// 通过标签重新验证
revalidateTag('products');Step 5: Handle Fallback Strategies
步骤5:处理回退策略
getStaticPaths fallback options:
typescript
export async function getStaticPaths() {
return {
paths: [...],
fallback: false, // 404 for non-pre-rendered paths
// fallback: true, // Generate on-demand, show loading
// fallback: 'blocking', // Generate on-demand, wait for page
};
}getStaticPaths回退选项:
typescript
export async function getStaticPaths() {
return {
paths: [...],
fallback: false, // 未预渲染路径返回404
// fallback: true, // 按需生成,显示加载状态
// fallback: 'blocking', // 按需生成,等待页面完成
};
}Common Patterns
常见模式
Hybrid Approach
混合策略
typescript
// Mix strategies in same app
// - SSG for marketing pages
// - ISR for blog posts
// - SSR for user dashboard
// - CSR for interactive features
// app/layout.tsx (SSG)
export default function RootLayout({ children }) {
return <html><body>{children}</body></html>;
}
// app/blog/[slug]/page.tsx (ISR)
async function BlogPost({ params }) {
const post = await fetch(`/api/posts/${params.slug}`, {
next: { revalidate: 60 }
});
return <Article post={post} />;
}
// app/dashboard/page.tsx (SSR)
async function Dashboard() {
const data = await fetch('/api/user', { cache: 'no-store' });
return <DashboardContent data={data} />;
}typescript
// 同一应用中混合使用不同策略
// - 营销页面用SSG
// - 博客文章用ISR
// - 用户仪表盘用SSR
// - 交互功能用CSR
// app/layout.tsx (SSG)
export default function RootLayout({ children }) {
return <html><body>{children}</body></html>;
}
// app/blog/[slug]/page.tsx (ISR)
async function BlogPost({ params }) {
const post = await fetch(`/api/posts/${params.slug}`, {
next: { revalidate: 60 }
});
return <Article post={post} />;
}
// app/dashboard/page.tsx (SSR)
async function Dashboard() {
const data = await fetch('/api/user', { cache: 'no-store' });
return <DashboardContent data={data} />;
}Optimistic UI with ISR
结合ISR的乐观UI
typescript
// Show stale data immediately, revalidate in background
export async function getStaticProps() {
const data = await fetchData();
return {
props: { data },
revalidate: 1, // Revalidate every second
};
}typescript
// 立即展示旧数据,后台重新验证
export async function getStaticProps() {
const data = await fetchData();
return {
props: { data },
revalidate: 1, // 每秒重新验证
};
}Conditional Rendering
条件渲染
typescript
// Different rendering based on route
export async function getServerSideProps(context) {
const { preview } = context;
if (preview) {
// SSR for preview mode
const data = await fetchDraftContent();
return { props: { data, preview: true } };
}
// Redirect to SSG version
return {
redirect: {
destination: '/static-version',
permanent: false,
},
};
}typescript
// 根据路由使用不同渲染方式
export async function getServerSideProps(context) {
const { preview } = context;
if (preview) {
// 预览模式下使用SSR
const data = await fetchDraftContent();
return { props: { data, preview: true } };
}
// 重定向到SSG版本
return {
redirect: {
destination: '/static-version',
permanent: false,
},
};
}Decision Matrix
决策矩阵
| Requirement | SSG | ISR | SSR | CSR |
|---|---|---|---|---|
| SEO Critical | ✅ | ✅ | ✅ | ❌ |
| Fast TTFB | ✅ | ✅ | ❌ | ❌ |
| Fresh Data | ❌ | ⚠️ | ✅ | ✅ |
| Personalized | ❌ | ❌ | ✅ | ✅ |
| Large Scale | ⚠️ | ✅ | ✅ | ✅ |
| Build Time | ❌ | ✅ | ✅ | ✅ |
✅ = Excellent, ⚠️ = Acceptable, ❌ = Poor
| 需求 | SSG | ISR | SSR | CSR |
|---|---|---|---|---|
| SEO关键 | ✅ | ✅ | ✅ | ❌ |
| 快速TTFB | ✅ | ✅ | ❌ | ❌ |
| 数据新鲜 | ❌ | ⚠️ | ✅ | ✅ |
| 个性化 | ❌ | ❌ | ✅ | ✅ |
| 大规模站点 | ⚠️ | ✅ | ✅ | ✅ |
| 构建时间 | ❌ | ✅ | ✅ | ✅ |
✅ = 优秀, ⚠️ = 可接受, ❌ = 不佳
Performance Considerations
性能考量
SSG:
- Fastest: Pre-rendered at build time
- Best for CDN caching
- Long build times for large sites
- Stale data until next build
ISR:
- Fast initial load (cached)
- Automatic updates
- Best of both worlds
- Slight delay for revalidation
SSR:
- Always fresh data
- Slower TTFB
- Higher server load
- Can't be cached at CDN edge
CSR:
- Slow initial load
- No SEO benefits
- Reduces server load
- Best for authenticated content
SSG:
- 速度最快:构建时预渲染
- 最适合CDN缓存
- 大型站点构建时间长
- 下次构建前数据会过时
ISR:
- 初始加载快(缓存)
- 自动更新
- 兼顾性能与新鲜度
- 重新验证时有轻微延迟
SSR:
- 数据始终新鲜
- TTFB较慢
- 服务器负载更高
- 无法在CDN边缘缓存
CSR:
- 初始加载慢
- 无SEO收益
- 降低服务器负载
- 最适合已认证内容
Troubleshooting
问题排查
Build taking too long:
- Use ISR instead of SSG
- Reduce number of pre-rendered paths
- Use fallback: 'blocking'
Stale data showing:
- Reduce revalidate time
- Implement on-demand revalidation
- Consider SSR for critical data
High server costs:
- Move from SSR to ISR where possible
- Implement proper caching
- Use CDN for static assets
SEO issues:
- Avoid CSR for public content
- Use SSG or SSR
- Implement proper metadata
构建时间过长:
- 用ISR替代SSG
- 减少预渲染路径数量
- 使用fallback: 'blocking'
显示过时数据:
- 缩短重新验证时间
- 实现按需重新验证
- 关键数据考虑使用SSR
服务器成本过高:
- 尽可能将SSR替换为ISR
- 实现合理缓存
- 静态资源使用CDN
SEO问题:
- 公开内容避免使用CSR
- 使用SSG或SSR
- 配置正确的元数据
Best Practices
最佳实践
- Start with SSG: Default to static, move to dynamic only when needed
- Use ISR for updates: Better than full SSR for most cases
- Combine strategies: Different pages can use different methods
- Cache aggressively: Use CDN and browser caching
- Monitor performance: Track TTFB, build times, server load
- Implement fallbacks: Handle errors and loading states
- Test thoroughly: Verify behavior in production
- 从SSG开始:默认使用静态渲染,仅在必要时切换到动态方式
- 用ISR处理更新:多数场景下比全SSR更优
- 组合使用策略:不同页面可采用不同方式
- 积极缓存:使用CDN和浏览器缓存
- 监控性能:跟踪TTFB、构建时间、服务器负载
- 实现回退机制:处理错误和加载状态
- 充分测试:在生产环境验证行为