← [ BACK_TO_NOTES ]

Next.js

Next.js 是建立在 React 之上的全栈 Web Framework;本页聚焦 App Router、Server/Client Components、渲染模型与应用服务器边界。

#resource #growing #frame #react

[!info] related notes

Next.js

这是什么

Next.js 是建立在 React 之上的全栈 Web Framework。React 主要负责组件与 UI 模型;Next.js 在其上整合路由、服务端渲染、React Server Components、服务端数据获取、HTTP 端点、Mutation、缓存、构建与部署约定。

它不是“React 的升级版”,也不是简单的“React + Express”。更稳定的理解是:

React          → UI / Component model
Next.js        → React application framework
App Router     → route + layout + server/client boundary
Server runtime → data / secret / database / HTTP boundary
Browser runtime→ interaction / browser API / client state

App Router 的核心心智模型

现代 Next.js 应先建立两个彼此独立的维度。

维度一:组件代码在哪里执行

  • Server Component:服务端执行;适合读取数据库、服务端 API、文件、Secret,并组织不依赖浏览器交互的 UI。
  • Client Component:需要客户端 JavaScript runtime;适合事件、局部状态、effect、window、document、localStorage 等浏览器能力。

在 App Router 中应采用 server-first 思路:默认保持 Server Component,只有出现真实客户端能力需求时才建立 "use client" 边界。

注意:显示在浏览器里的 UI 不等于 Client Component。只根据 props 显示姓名、BMI、文章正文等内容,完全可以由 Server Component 负责。

维度二:Route 什么时候生成

  • Static rendering / SSG:构建时或再验证时生成并缓存结果。
  • Dynamic rendering / SSR-like request-time rendering:请求到来时根据用户、cookie、搜索参数或最新数据生成。
  • CSR:主要内容依赖浏览器 JavaScript 在客户端生成或补取。

因此:

Server Component ≠ SSR
Client Component ≠ CSR

Server Component 可以参与静态生成,也可以参与请求时动态渲染;Client Component 的初始 UI 也可以被预渲染,随后在浏览器 hydration。

"use client" 真正表示什么

"use client" 不是“禁止服务端碰这个组件”,而是建立 Server → Client 模块边界:该模块及其客户端依赖需要进入客户端组件图,能够在浏览器中执行和 hydration。

常见判断:

数据库 / Secret / 服务端 I/O      → Server
普通只读 JSX                     → 优先 Server
useState / onClick / onChange    → Client
window / document / localStorage → Client

把 Client Component 设计成较小的交互边界,通常可以减少客户端 JavaScript 与 hydration 成本。

一个典型组件树

AssessmentPage           Server
├── AssessmentSummary    Server
├── BMIResult            Server
└── WeightEditor         Client
    └── SaveButton       Client interaction
         ↓
      server mutation / action
         ↓
      database

这里 SaveButton 不能简单标成“纯 Server”:点击事件发生在浏览器,真正的数据写入属于服务器。一个用户动作可以跨越 Client 与 Server 两个边界。

App Router 路由入口

App Router 不再要求维护一份独立的 route 配置表,而是让文件系统本身成为路由声明语言。

核心入口拆成四个原子知识点:

最短模型:

folder      → URL segment
page.tsx    → public page
[id]        → dynamic segment
(group)     → organize without URL
<Link>      → client-side navigation / prefetch

文件系统结构可以反映产品的信息架构,但不应机械复制内部模块层级;URL nesting 也不能替代数据库关系与授权校验。

App Router 的 Route Boundary

App Router 不只有“URL → page.tsx”。每个 route segment 还可以定义共享外壳、等待状态、异常状态和资源不存在状态:

Route Segment
│
├── layout.tsx
│     ↓
│   shared / preserved shell
│
└── Route Content State
      ├── loading.tsx
      │     ↓ pending / streaming
      ├── page.tsx
      │     ↓ success
      ├── error.tsx
      │     ↓ unexpected failure
      └── not-found.tsx
            ↓ expected resource absence

对应原子笔记:

最重要的状态区分是:

slow / pending
→ loading.tsx

success
→ page.tsx

DB timeout / unexpected code failure
→ error.tsx

query succeeds, core resource missing
→ notFound()
→ not-found.tsx

同时,外层仍然有效的 Layout 可以继续保留,因此局部 loading、error 或 not-found 不意味着整棵应用树都要重建。

App Router 数据获取入口

数据获取不能只问“Server 还是 Client”,而要同时回答:首屏是否需要、谁拥有数据、运行期是否需要持续同步。

核心入口:

最短模型:

首屏需要?
→ 优先 Server Component

数据是谁拥有?
→ 当前 App owner:data layer / DB
→ 独立 Service owner:service API

页面运行后持续同步?
→ TanStack Query / SWR / SSE / WebSocket

多个请求互不依赖?
→ 并行启动

特别要避免两个误区:

Client Component ≠ 必须 Client Fetch
Server Component 能访问 DB ≠ 应该绕过 Domain Service

Cache / Revalidation 入口

数据获取回答“去哪里读”,缓存进一步回答“什么时候可以复用之前的读结果、什么时候必须重新向 authority 收敛”。

当前 Cache Components 模型可以先压缩成:

要复用 server result?
→ "use cache"

时间 freshness 怎么定义?
→ cacheLife(stale / revalidate / expire)

业务数据明确变化?
→ cacheTag + on-demand revalidation

对应原子笔记:

最关键的决策:

长期稳定、允许复用
→ use cache + cacheLife

数据变化后允许短暂 stale
→ revalidateTag(tag, "max")

用户刚写完,下一次 read 必须看到 fresh
→ updateTag(tag)

高频 runtime state
→ 不用长缓存模拟实时性
→ fresh snapshot + SSE / WebSocket

还要避免三个非常容易混淆的等式:

cacheTag ≠ queryKey
updateTag ≠ optimistic update
Next.js Cache ≠ TanStack Query Cache

更准确的分层是:

Owning Service / DB
→ authoritative truth

Next.js Cache
→ server-side projection

TanStack Query
→ client-side server-state projection

因此两者不是重复实现:一个主要减少服务器读取/渲染成本,一个主要管理浏览器运行期的远程状态同步。

Route Handler / HTTP Endpoint 入口

route.ts 是 App Router 的 HTTP transport boundary,不是另一种 React Page:

page.tsx
→ URL → React UI

route.ts
→ URL → HTTP Response

对应原子笔记:

最短调用规则:

Server Component
→ 同应用业务能力
→ 直接调用 Service / Data Layer

Server Component
→ 独立 Owning Service
→ 调真正的 Service API

Browser / Mobile / External System
→ 需要 HTTP contract
→ Route Handler

Stripe / CMS / GitHub Webhook
→ Route Handler

这里最重要的不是 Next.js 发明了新后端分层,而是同一个框架同时拥有:

React Server Component
+ HTTP Route Handler

所以必须明确:

内部 server call
→ share Service
→ 不要为了复用而绕 HTTP

跨 runtime / 跨系统
→ HTTP boundary
→ Route Handler

Route Handler 本身仍然遵循普通后端工程原则:

HTTP Request
→ parse / validate
→ authenticate
→ authorize
→ Service
→ Repository / Owning Service
→ HTTP Response

尤其:

session exists
≠
can access resource

URL id
≠
authorization proof

因此 /api/assessments/123 必须继续验证当前用户对 Assessment 123 的 object-level permission,避免 IDOR / BOLA。

Server Actions / Mutation 入口

Server Action 为 React UI 提供一个直接的 server-side Mutation 边界。它简化的是 transport 样板,不会取消正常的后端工程约束。

核心原子笔记:

最短模型:

Server Component
→ Read / Render

Server Action
→ React UI Mutation

Route Handler
→ Explicit HTTP Contract

Server Action 典型链路:

React form / button
→ Server Action
→ validate
→ authenticate
→ authorize
→ Application Service
→ Repository / Owning Service API
→ authoritative state changes
→ revalidate cache
→ redirect / fresh UI

例如 MemoFlow Web 创建 Goal:

Create Goal
→ Server Action
→ goalService.create()
→ DB

但 MemoFlow iOS App 需要创建 Goal 时,应走稳定 HTTP Contract:

iOS
→ POST /api/goals
→ Route Handler
→ goalService.create()

如果真正数据 owner 是 BodySense Go Backend:

React UI
→ Server Action
→ Go API
→ Go Service
→ DB

不能因为 Action 在服务器运行就绕过 Go 直接写它拥有的数据库。

最重要的安全提醒:

Server Action argument
= client-controlled / untrusted input

UI hides button
≠ authorization

所以 Server Action 和 Route Handler 一样,必须在服务端重新验证输入、身份与对象级权限。

全栈不等于“不要独立后端”

Next.js 可以承担很多 Web application server / BFF 职责,例如:

  • 页面与路由
  • 认证入口
  • CRUD
  • Route Handlers
  • Server Actions / Mutation
  • 数据聚合
  • Web 层缓存

但复杂系统仍可以保留独立服务。判断标准不是“Next.js 能不能写后端”,而是:

这段逻辑属于 Web application boundary,还是一个需要独立演进、独立扩缩容、独立权限/运行时的业务服务?

例如 AI 应用可以保持:

Next.js Web / BFF
      ↓
Domain Backend
      ↓
Agent Runtime / Worker
      ↓
Database

性能价值应该怎么理解

Server Components 的主要价值不是保证“总网络字节一定更少”,而是可以让很多实现代码永远不进入客户端 bundle:

  • 数据库 SDK
  • 服务端数据整理逻辑
  • 只用于生成 UI 的大型依赖
  • Secret 与内部连接逻辑

常见收益是:

client JS ↓
parse / execute JS ↓
hydration scope ↓
unnecessary client→API round trip ↓

但真实性能仍取决于 TTFB、缓存、数据访问、流式策略和客户端交互复杂度,不能把 Server Components 当作自动性能银弹。

与 Astro 的联系

Next.js 的 server-first + client boundaries 与 Islands Architecture 在目标上有相似之处:都希望只给真正需要交互的区域发送客户端 JavaScript。

但机制不同:

  • Astro 更偏 HTML-first + 独立 hydration islands。
  • Next.js App Router 更偏 统一 React Tree + RSC / Client Component boundary。

因此“Client Component 是一个岛”可以作为学习类比,但不能把两种机制视为同一个架构。

选择边界时的最短判断

需要浏览器能力?
  ├─ 否 → 优先 Server Component
  └─ 是 → Client Component

需要最新/个性化请求时数据?
  ├─ 否 → 优先 Static rendering
  └─ 是 → Dynamic request-time rendering

这两个问题要分开回答。

创建于 2026/9/15 更新于 2026/9/16
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本