前端工程Technical Deep Dive

首屏优化利器 —— SSR、SSG、ISR 与流式渲染

发布时间2026/03/29
分类前端工程
预计阅读2 分钟
作者吴长龙
*

传统 SPA 的问题

01.内容

传统 SPA 的问题

javascript snippetjavascript
// 传统 SPA 渲染流程
// 1. 用户请求 HTML (≈50ms)
// 2. 返回空 HTML + JS
// 3. 浏览器下载 JS (500KB+ ≈ 2s)
// 4. JS 执行 → 请求 API (200ms)
// 5. 渲染页面 (100ms)
// 6. 用户看到内容

// Total: ≈ 3s+ (首屏)

问题:用户需要等待所有 JS 下载、执行完成才能看到内容。

解决方案对比

方案首屏SEO交互性适用场景
CSR后台系统
SSR动态内容
SSG最快静态页面
ISR内容站点
混合复杂应用

SSR (服务端渲染)

#### Next.js SSR

jsx snippetjsx
// pages/post/[id].js
export async function getServerSideProps(context) {
  const { id } = context.params;
  
  // 服务端获取数据
  const post = await fetch(`https://api.example.com/posts/${id}`)
    .then(res => res.json());
  
  return {
    props: { post }
  };
}

export default function Post({ post }) {
  return <h1>{post.title}</h1>;
}

#### 渲染流程

code snippetcode
用户请求 → Node.js 服务器
          → 获取数据
          → 渲染 React 组件为 HTML
          → 返回完整 HTML
          → 浏览器直接显示
          → 同时下载 JS
          → hydration (水合)
          → 变为可交互

#### Next.js App Router (默认 RSC)

jsx snippetjsx
// app/posts/[id]/page.tsx
async function PostPage({ params }) {
  const { id } = params;
  
  // 默认服务端组件,直接获取数据
  const post = await getPost(id);
  
  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
    </article>
  );
}

SSG (静态站点生成)

#### Next.js SSG

jsx snippetjsx
// pages/about.js
export async function getStaticProps() {
  const data = await fetchData();
  
  return {
    props: { data },
    revalidate: 60  // ISR: 60秒后重新生成
  };
}

export default function About({ data }) {
  return <div>{data.content}</div>;
}

// 或 App Router
// app/about/page.tsx (默认静态)
async function AboutPage() {
  const data = await getData();
  return <div>{data.content}</div>;
}

#### 构建时生成

code snippetcode
构建阶段:
pages/index.html     (已渲染)
pages/about.html     (已渲染)
pages/posts/1.html   (已渲染)
pages/posts/2.html   (已渲染)
...

用户请求 → 直接返回静态 HTML

#### 适用场景

  • 文档网站
  • 博客
  • 营销页面
  • 产品展示

ISR (增量静态再生成)

#### 工作原理

code snippetcode
首次访问 → 生成页面 → 缓存
                      ↓
n 秒后再次访问 → 后台重新生成 → 更新缓存

#### Next.js ISR 实现

jsx snippetjsx
// App Router
export const revalidate = 60; // 60秒重新验证

async function BlogPage() {
  const posts = await getPosts();
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

// 指定路由
export const dynamicParams = true;

export async function generateStaticParams() {
  const posts = await getPosts();
  return posts.map(post => ({ slug: post.slug }));
}

#### On-Demand ISR

jsx snippetjsx
// 通过 API 触发重新生成
// app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache';

export async function POST(request) {
  const body = await request.json();
  
  if (body.secret !== process.env.REVALIDATE_SECRET) {
    return Response.json({ message: 'Invalid token' }, { status: 401 });
  }
  
  revalidatePath('/posts');
  revalidatePath('/posts/[slug]');
  
  return Response.json({ revalidated: true });
}

// Webhook 触发
// CMS 发布文章 → 触发 Webhook → 重新生成

流式渲染 (Streaming SSR)

#### 传统 SSR vs 流式

code snippetcode
传统 SSR:
[请求] → [获取数据] → [渲染HTML] → [返回]
              ↓ (慢,阻塞)
         可能需要 500ms+

流式渲染:
[请求] → [流式返回] → [逐步显示]
         ↓
    更快响应,首屏更快

#### Next.js 流式

jsx snippetjsx
// App Router 自动流式
// 1. 立即返回布局
// 2. 挂起的数据在解决后流式注入

import { Suspense } from 'react';

async function PostPage({ params }) {
  return (
    <article>
      {/* 立即渲染 */}
      <h1>Post Title</h1>
      
      {/* 流式加载 */}
      <Suspense fallback={<CommentsSkeleton />}>
        <Comments postId={params.id} />
      </Suspense>
    </article>
  );
}

async function Comments({ postId }) {
  const comments = await getComments(postId);
  return comments.map(c => <div key={c.id}>{c.text}</div>);
}

#### 完整示例

jsx snippetjsx
// app/posts/[slug]/page.tsx
import { Suspense } from 'react';

export default async function PostPage({ params }) {
  return (
    <main>
      <article>
        <PostContent slug={params.slug} />
      </article>
      
      <aside>
        <Suspense fallback={<RelatedSkeleton />}>
          <RelatedPosts category="tech" />
        </Suspense>
        
        <Suspense fallback={<CommentsSkeleton />}>
          <CommentsSection postId={params.slug} />
        </Suspense>
      </aside>
    </main>
  );
}

async function PostContent({ slug }) {
  const post = await getPost(slug);
  return (
    <>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
    </>
  );
}

混合渲染策略

jsx snippetjsx
// Next.js App Router 允许混合使用

// 1. 静态页面 (默认)
function AboutPage() { ... }

// 2. 动态页面
export const dynamic = 'force-dynamic';
function SearchPage({ searchParams }) { ... }

// 3. 按需重新验证
export const revalidate = 3600;
function BlogPage() { ... }

// 4. 运行时
export const runtime = 'nodejs';
function ApiPage() { ... }

性能优化实践

#### 1. 减少首屏数据

jsx snippetjsx
// ❌ 不好:加载所有数据
async function PostPage() {
  const post = await getFullPost(); // 包含正文、评论、相关...
  return <Post post={post} />;
}

// ✅ 好:分步加载
async function PostPage() {
  return (
    <article>
      <PostContent postId={id} />
      <Suspense fallback={<CommentsSkeleton />}>
        <Comments postId={id} />
      </Suspense>
    </article>
  );
}

#### 2. 骨架屏

jsx snippetjsx
function PostSkeleton() {
  return (
    <div className="skeleton">
      <div className="title skeleton-item" />
      <div className="content skeleton-item" />
    </div>
  );
}

#### 3. 流式 UI

jsx snippetjsx
function Dashboard() {
  return (
    <div>
      {/* 关键指标立即显示 */}
      <StatsOverview />
      
      {/* 次要内容流式加载 */}
      <Suspense fallback={<ChartSkeleton />}>
        <RevenueChart />
      </Suspense>
      
      <Suspense fallback={<TableSkeleton />}>
        <RecentOrders />
      </Suspense>
    </div>
  );
}

总结

渲染模式首屏时间适用场景
CSR后台系统
SSR需 SEO + 实时数据
SSG静态内容
ISR内容站点、博客
流式复杂页面

现代前端推荐:Next.js App Router,默认静态,按需动态。