Next.js 服务端渲染与性能优化实践

很多团队在引入 Next.js 后,第一个困惑就是:SSR、SSG、ISR 到底有什么区别?我该用哪种?本文从渲染策略的底层差异讲起,结合 App Router 的实际用法,分享一线性能优化实践。

一、三种渲染策略的本质区别

策略渲染时机数据新鲜度适合场景
SSR(服务端渲染)每次请求时实时强个性化、动态数据
SSG(静态生成)构建时构建时快照内容型页面、营销页
ISR(增量静态再生)构建时 + 后台定时按 revalidate 间隔刷新博客、商品页等半动态内容

简单记忆:SSG 是"提前做好",SSR 是"现做现卖",ISR 是"提前做好 + 定期重做"。它们的核心权衡点都是同一个:首屏速度 vs 数据新鲜度

二、App Router 中的实践

Next.js 13+ 的 App Router 中,通过函数命名声明渲染策略,而不是配置文件,这大大降低了心智负担。

2.1 静态页面(默认)

// app/about/page.tsx
// 不带任何动态 API 时,默认就是静态生成
export default function About() {
  return <h1>关于我们</h1>;
}

2.2 动态渲染(SSR)

// app/dashboard/page.tsx
// 读取 cookies / headers / searchParams 时自动变为动态渲染
export default async function Dashboard() {
  const user = await getUser();
  return <Profile user={user} />;
}

2.3 ISR

// app/posts/[id]/page.tsx
// 每 60 秒后台重新生成一次
export const revalidate = 60;

export default async function Post({ params }: { params: { id: string } }) {
  const post = await fetch(`https://api.example.com/posts/${params.id}`,
    { next: { revalidate: 60 } });
  return <Article data={await post.json()} />;
}

还有更细粒度的 on-demand revalidation:通过调用 revalidatePath()revalidateTag(),在数据变更(如 CMS 发布文章)时精确失效对应缓存,实现"静态的性能 + 实时的内容"。

三、性能优化实践清单

3.1 图片优化:Image 组件

不要使用原生 <img>,务必使用 next/image,它自动提供:

  • WebP/AVIF 格式转换与多尺寸响应式裁切;
  • 懒加载(viewport 外图片自动 lazy);
  • CLS 消除:显式设置 width/height 或使用 fill,避免布局偏移;
  • CDN 缓存与渐进式加载。
import Image from "next/image";

<Image
  src="/banner.png"
  alt="banner"
  width={1200}
  height={400}
  priority          // 首屏关键图设置 priority,立即加载
  sizes="(max-width: 768px) 100vw, 50vw"
/>

3.2 字体优化:next/font

next/font 会自动内联字体文件、开启字体子集化,避免布局偏移(FOIT),也避免额外的字体请求:

import { Inter } from "next/font/google";

const inter = Inter({ subsets: ["latin"] });

export default function RootLayout({ children }) {
  return <html lang="zh-CN" className={inter.className}>{children}</html>;
}

3.3 流式渲染与 Suspense

把慢速数据源包进 <Suspense>,页面会先返回骨架屏,数据就绪后流式填充,大幅降低 TTFB 感知:

export default function Page() {
  return (
    <div>
      <Header />            {/* 立即渲染 */}
      <Suspense fallback={<Skeleton />}>
        <SlowList />        {/* 数据就绪后流式注入 */}
      </Suspense>
    </div>
  );
}

3.4 缓存与请求合并

  • 利用 Next.js 的请求记忆(Request Memoization):同一渲染周期内相同的 fetch 请求自动去重;
  • 开启 CDN 缓存:对 ISR 页面设置合适的 Cache-Control
  • 服务端组件中尽量直接查询数据库(如用 Prisma/Drizzle),省去一层 HTTP 开销。

四、常见陷阱

陷阱原因解决方案
"use client" 滥用整页转客户端渲染,丢失 SSG 优势尽量让叶子组件客户端化,保持页面服务端
在客户端组件里 fetch重复请求、无法使用服务端缓存数据获取移到服务端组件,通过 props 下传
忽略 revalidate 策略ISR 页面内容长期陈旧配合 on-demand revalidation 精确刷新
图片未指定尺寸CLS 分数飙升统一使用 next/image 并声明宽高

五、总结

Next.js 的渲染能力是"同一套代码,按页选择策略":营销页用 SSG 拿到满分性能分,个人中心用 SSR 保证数据实时,内容页用 ISR 兼顾两者,再叠加图片、字体、流式渲染三板斧,一个高分的应用就这么搭出来了。

记住核心原则:默认静态,按需动态,缓存为王。