Next.js 基础知识

Table of contents

use client 指令

可以,简单总结就是:

use client 是什么

它是 Next.js 里的一个标记,表示:

这个组件要在浏览器端运行。

写了它之后,这个组件就变成 Client Component


加了之后会怎样

1. 可以使用浏览器相关能力

比如:

  • useState
  • useEffect
  • 点击事件 onClick
  • window
  • localStorage

这些都是客户端能力。


2. 这部分代码会进入客户端 JS

也就是浏览器要下载这部分 JavaScript,组件才能有交互。


3. 会增加前端负担

如果 use client 用太多:

  • JS 变大
  • hydration 变多
  • 首屏性能可能变差
  • SEO 也可能间接受影响

什么时候用

当组件需要:

  • 交互
  • 状态
  • 副作用
  • 浏览器 API

就用。

比如:

  • 按钮切换
  • 弹窗
  • 表单输入
  • Tabs
  • 轮播图

什么时候不要用

如果只是:

  • 展示文字
  • 渲染文章
  • 显示列表
  • 服务端拿数据后展示

那一般不要加,保持 Server Component 更好。


use client 不是不能用,而是要少用、精确用。

use client = 这个组件要在浏览器里“活起来”。

Next.js Image

属性

实践:在 Cloudflare Workers 上完美使用 Next.js Image 组件

SSG / 静态站点生成

核心场景

在构建时把页面直接生成成 HTML(以及需要的静态资源),上线后用户访问时直接用 CDN/缓存秒开

  • 在构建build的时候,生成的静态页面
  • 所有的请求,看到的都是同一个页面
  • 生成的静态资源,可以放在 CDN,每次请求都会缓存

SSG VS SSR VS ISR

SSG 是“提前生成静态页面”,SSR 是“每次请求时生成页面”。

模式 构建时 请求时 页面特点
SSG ✅ 生成 HTML ❌ 不再计算 内容稳定、加载快
SSR ✅ 服务器渲染 内容实时、响应慢
ISR ✅+定时更新 ✅ 可自动刷新 性能与实时性平衡

SSR

SSR 是“每次请求时生成页面”。

ISR / Incremental Static Regeneration / 增量静态再生

ISR 允许开发者在构建时生成静态页面,并在部署后定期或按需更新这些页面,而无需重新构建整个网站。

通过 revalidate 配置实现。

工作原理

  1. 初始构建: 在 next build 期间,Next.js 会预渲染页面并将其作为静态 HTML 文件生成。
  2. 提供缓存页面: 当用户访问该页面时,会立即从缓存中提供静态生成的页面,从而实现极快的加载速度。
  3. 触发重新验证: 如果请求在设定的重新验证时间之后到达,Next.js 会在后台重新生成该页面。在此期间,用户仍然会看到旧的(过期的)缓存页面。
  4. 更新缓存: 一旦页面成功重新生成,Next.js 会用新页面更新缓存。此后的请求将收到更新后的内容。

ISR 的生命周期

  1. 首次访问 (或缓存清空后):
    • 没有缓存。Vercel 在服务器端渲染 (SSR) 页面。
    • 页面生成后,返回给用户,并存入边缘缓存 (Edge Cache)。
    • 这次操作会触发一次 ISR Write (写入缓存)。
  2. 60 秒内的后续访问:
    • 缓存是“新鲜的”(fresh)。
    • Vercel 直接从边缘缓存返回页面,速度极快。
    • 这次操作是 Cache HIT,不计入 ISR Reads 或 Writes。这是最理想的情况。
  3. 60 秒过后的第一次访问:
    • 缓存现在是“过期的”(stale)。
    • 关键点来了:Vercel 立即从缓存中返回这个“过期”的页面给用户,保证了用户的访问速度。
    • 这次 “返回过期页面” 的操作,被计算为一次 ISR Read。
    • 同时,Vercel 在后台悄悄地触发一次页面重新生成。
  4. 后台重新生成:
    • Vercel 再次执行 getStaticProps,获取新数据,生成新页面。
    • 当新页面生成成功后,它会用新页面替换掉缓存中的旧页面。
    • 这次 “替换缓存” 的操作,被计算为一次 ISR Write。
  5. 重新生成期间的其它访问:
    • 如果在后台重新生成(可能需要几秒钟)还未完成时,又有其他用户访问该页面,他们同样会收到那个“过期”的页面。
    • 每一次这样的访问,都会被计算为一次 ISR Read。

RSC(React Server Component)

Server Side Rendering

generateStaticParams

环境变量

私有变量

NEXT_PUBLIC_ 的变量只存在于 node.js 环境,在 brower 中读取不到。

公开变量

Build Time(构建时) 和 Runtime(运行时)

Build Time(构建时)

Build Time 是指在部署前执行 next build 命令时发生的阶段。 在这个阶段,Next.js 会:

主要任务:

  • 编译和打包代码(TypeScript → JavaScript,JSX → JavaScript)
  • 静态分析和优化 生成静态页面(Static Site Generation - SSG) 执行 getStaticProps 和 getStaticPaths 代码分割和 bundle 优化 图片优化和资源处理

特点:

  • 只执行一次(除非重新构建)
  • 可以执行耗时操作,因为不影响用户体验
  • 生成的文件存储在 .next 文件夹中
  • 适合数据预获取和预渲染

Runtime(运行时)

Runtime 是指应用程序在生产环境中实际运行时的阶段。包括:

主要任务:

  • 处理用户请求
  • 执行服务器端渲染(SSR)
  • 运行 API 路由
  • 执行 getServerSideProps
  • 处理动态路由
  • 中间件执行

特点:

  • 每次用户访问都会执行
  • 需要快速响应,影响用户体验
  • 可以访问请求上下文(cookies、headers 等)
  • 适合个性化内容和实时数据
  • Cloudflare Workers 的 wrangler.toml 中定义的环境变量在 Runtime(运行时) 阶段才能获取到,而不是在 Build Time

假设代码

 1// page.tsx
 2import ClientShell from './ClientShell'
 3import ServerArticle from './ServerArticle'
 4
 5export default function Page() {
 6  return (
 7    <ClientShell>
 8      <ServerArticle />
 9    </ClientShell>
10  )
11}
1// ClientShell.tsx
2'use client'
3
4export default function ClientShell({ children }) {
5  return <div>{children}</div>
6}

总览

 1flowchart TD
 2
 3    App["Next.js / React App"]
 4
 5    App --> RT["React Render Tree"]
 6    App --> MG["Module Dependency Graph"]
 7
 8    RT --> RTQ["回答:UI 谁包着谁?"]
 9    RT --> Example1["Client Parent<br/>↓<br/>Server Child<br/>可以存在"]
10
11    MG --> MGQ["回答:代码谁 import 谁?"]
12
13    MG --> ServerGraph["Server Module Graph"]
14
15    ServerGraph --> Boundary["'use client'<br/>Client Boundary"]
16
17    Boundary --> ClientGraph["Client Module Graph"]
18
19    ClientGraph --> DependencyA["Client Module A"]
20    ClientGraph --> DependencyB["Client Module B"]
21    ClientGraph --> DependencyC["Client Module C"]
22
23    ClientGraph --> Bundle["Bundling"]
24
25    Bundle --> Chunk1["chunk A"]
26    Bundle --> Chunk2["chunk B"]
27    Bundle --> Chunk3["chunk C"]
28
29    Chunk1 --> Initial["Initial JS"]
30    Chunk2 --> Lazy["Lazy-loaded JS"]
31    Chunk3 --> Nav["Navigation-loaded JS"]
32
33    Boundary --> Island["页面视角:Client Island"]

Client Boundary

Server module graph 和 Client module graph 之间的分界线。

 1flowchart TD
 2    Page["page.tsx<br/>Server"]
 3
 4    Boundary["SearchBox.tsx<br/>'use client'<br/>Client Boundary"]
 5
 6    Input["SearchInput"]
 7    Button["ClearButton"]
 8    Dropdown["Dropdown"]
 9
10    Page -->|"import"| Boundary
11    Boundary --> Input
12    Boundary --> Button
13    Boundary --> Dropdown

Module Dependency Graph

只关心: 谁 import 谁?

 1flowchart TD
 2    Page["page.tsx<br/>Server Module"]
 3
 4    ClientShell["ClientShell.tsx<br/>'use client'"]
 5    ServerArticle["ServerArticle.tsx<br/>Server Module"]
 6
 7    Page -->|"import"| ClientShell
 8    Page -->|"import"| ServerArticle
 9
10    ClientShell --> ClientGraph["Client Module Graph"]

React Render Tree

页面最终的组件嵌套关系是什么?

 1flowchart TD
 2    Page["Page<br/>Server"]
 3    ClientShell["ClientShell<br/>Client"]
 4    Div["div"]
 5    ServerArticle["ServerArticle<br/>Server"]
 6    Article["article"]
 7
 8    Page --> ClientShell
 9    ClientShell --> Div
10    Div --> ServerArticle
11    ServerArticle --> Article
1Render parent
23Module import parent

Client Graph

Client module graph:从 ‘use client’ 入口沿着 import dependency 往下形成的一组 Client modules。