ssr-ssg-advisor

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

SSR/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:
  1. Does content change per user? (personalization)
  2. How frequently does content update?
  3. Is SEO critical?
  4. What's the acceptable data freshness?
  5. How many pages need to be generated?
思考以下问题:
  1. 内容是否因用户而异?(个性化需求)
  2. 内容更新频率如何?
  3. SEO是否关键?
  4. 可接受的数据新鲜度是多少?
  5. 需要生成多少页面?

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: CSR
App 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: CSR

Step 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

决策矩阵

RequirementSSGISRSSRCSR
SEO Critical
Fast TTFB
Fresh Data⚠️
Personalized
Large Scale⚠️
Build Time
✅ = Excellent, ⚠️ = Acceptable, ❌ = Poor
需求SSGISRSSRCSR
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

最佳实践

  1. Start with SSG: Default to static, move to dynamic only when needed
  2. Use ISR for updates: Better than full SSR for most cases
  3. Combine strategies: Different pages can use different methods
  4. Cache aggressively: Use CDN and browser caching
  5. Monitor performance: Track TTFB, build times, server load
  6. Implement fallbacks: Handle errors and loading states
  7. Test thoroughly: Verify behavior in production
  1. 从SSG开始:默认使用静态渲染,仅在必要时切换到动态方式
  2. 用ISR处理更新:多数场景下比全SSR更优
  3. 组合使用策略:不同页面可采用不同方式
  4. 积极缓存:使用CDN和浏览器缓存
  5. 监控性能:跟踪TTFB、构建时间、服务器负载
  6. 实现回退机制:处理错误和加载状态
  7. 充分测试:在生产环境验证行为