Next.js Fundamentals, v4

了解构建高性能 Next.js 应用所需的一切!深入理解 React Server Components 和 Server Actions。创建一个同时利用静态路由和动态路由的应用。添加更复杂的功能,如身份验证、使用 dynamicIO 进行缓存以及 edge functions。将你的应用部署到 Vercel,并展示你专业的 Next.js 技能!

0-introduction

讲师介绍

Scott Moss - Frontend Masters 讲师

  • 目前是 Double Zero 公司联合创始人(doublezero.tech)
  • 专注于 AI 代理技术,用户只需描述需求,代理自动执行
  • 所有项目都使用 Next.js 构建,部署在 Vercel 平台
  • 曾任旧金山风投公司投资人,专注 AI 公司投资
  • Netflix 前工程师,负责随机创意项目开发

教学理念

核心原则

  • 只教授生产环境实际使用的技术
  • 基于实际经验和踩过的坑分享见解
  • 强调从错误中学习,形成个人观点

学习方法论

  • 记忆 vs 学习的区别:真正的学习需要在舒适区边缘实践
  • 最佳学习状态:略显不适但可以解决的挑战
  • 通过构建实际应用来加深理解
  • 鼓励使用资源、提问和查阅文档

课程概览

学习目标

  • 覆盖 Next.js 基础知识的 65-75%
  • 重点关注通用核心概念
  • 其余 25-35% 依据具体用例而定

项目介绍

构建应用:Mode(Linear 应用克隆)

技术栈覆盖

  • 身份验证(Auth)
  • 静态页面
  • 应用程序开发
  • API 路由
  • Server Actions
  • 其他 Next.js 核心功能

课程资源

GitHub 仓库

  • 仓库地址:Hendrixer/Next.js-Fundamentals
  • 分支结构
    • 每个课程对应不同分支
    • 起始分支:课程开始的代码
    • 解决方案分支:完成后的代码

学习材料

  • 文档格式:Markdown 文件(替代 Notion)
  • 位置:直接放在项目中
  • 用途
    • 学员学习参考
    • 讲师教学大纲
    • 完整代码示例

Next.js 发展现状

框架优势

  • Vercel 团队持续改进
  • 缓存机制更加流畅
  • 整体架构更加精简

技术环境

  • JavaScript 开发的黄金时期
  • AI 技术与前端开发深度融合
  • Next.js 在现代 Web 开发中的重要地位

学习建议

实践导向

  • 通过构建真实应用学习概念
  • 在实践中遇到问题并解决
  • 培养独立解决问题的能力

资源利用

  • 充分利用 Next.js 官方文档
  • 参考课程提供的代码示例
  • 积极提问和交流

心态调整

  • 接受学习过程中的困难
  • 将挑战视为成长机会
  • 从错误中总结经验

1-course-project-setup

Next.js 是什么

定义和特点

  • Next.js 是一个基于 React 的框架
  • React 是 UI 库而不是框架,Next.js 是使用 React 的框架
  • 适合构建混合应用(hybrid applications)

混合应用的概念

  • 混合应用同时具备静态网站和动态应用的特性
  • 包含静态页面(如营销网站)
  • 也包含客户端动态功能和服务器端功能
  • Next.js 使这种转换变得非常流畅

Next.js 的优势

  • 提供了大量开箱即用的功能
  • 无需围绕 React 构建自己的框架和意见
  • 简化了混合应用的开发过程

环境要求

Node.js 版本要求

  • 至少需要 Node.js 20 版本
  • 也支持 Bun,但课程统一使用 Node 20
  • 推荐使用 NVM (Node Version Manager) 管理 Node 版本

项目演示

静态页面部分

  • 功能介绍页面
  • 价格页面
  • FAQ 页面
  • 这些都是静态页面

动态功能部分

  • 用户注册/登录功能
  • CRUD 操作界面:
    • 创建问题
    • 查看问题列表
    • 编辑问题
    • 删除问题

特殊功能

  • 包含加载状态显示
  • 后台有意添加延迟以展示加载效果

项目设置步骤

1. 克隆和安装

# 克隆项目仓库
git clone [仓库地址]

# 安装依赖包
npm install

2. 切换分支

# 切换到第3课分支开始编码
git checkout 03

3. 环境变量配置

创建 .env 文件

  • 项目中有 .env.example 文件作为参考
  • 需要创建自己的 .env 文件
  • 环境变量是服务器端的动态变量,用于保护敏感信息

必需的环境变量

  1. JWT_SECRET

    • 用于身份验证
    • 可以设置为任意字符串
    • 具体实现不是课程重点
  2. DATABASE_URL

    • 数据库连接字符串
    • 有两种获取方式

4. 数据库设置选项

选项一:本地 Postgres

  • 在本地运行 Postgres 服务器
  • 创建数据库
  • 使用本地数据库 URL
  • Mac 上推荐使用 Postgres 应用

选项二:使用 Instagres(推荐)

  • 访问 instagres.com/new
  • 通过机器人验证
  • 获得免费的数据库 URL
  • 基于 Neon 的无服务器 Postgres 服务
  • 数据库会在 1 小时后过期(除非添加到 NEON 账户)

5. 数据库同步

# 同步数据库架构
npm run db:push

命令作用

  • 将数据库架构与数据库进行同步
  • 首次运行会显示检测到更改
  • 后续运行如无更改会显示 "no changes detected"
  • 设置完成后无需再次操作

课程结构说明

代码准备情况

  • 项目中已包含部分预写代码
  • 主要是 Tailwind CSS 样式等基础代码
  • 课程专注于 Next.js 特有功能
  • 会讲解如何从零开始创建 Next.js 应用

学习重点

  • 专注于 Next.js 特定功能
  • 不会详细讲解与 Next.js 无关的内容(如具体的身份验证实现)
  • 重点学习 Next.js 相关的身份验证部分

2-create-a-next-js-app-from-scratch

前置准备

  • 需要先设置数据库和环境变量文件
  • 初始应用可能会出现错误,这是故意设置的,用于后续修复练习

使用 NPX 创建 Next.js 应用

NPX 工具介绍

  • NPX 允许直接使用 NPM 包而无需全局安装
  • 适用于那些不想下载但需要使用的全局包

创建命令

npx create-next-app@canary [项目目录名]

示例:

npx create-next-app@canary todos
  • @canary 表示使用 Canary 版本(最新开发版本)
  • todos 是项目目录名

安装配置选项

必选配置项

  1. TypeScript:

    • 强烈建议选择"是"
    • 即使不使用类型,也没有额外负担
  2. ESLint:

    • 绝对选择"是"
    • 代码质量检查工具
  3. Tailwind CSS:

    • 推荐选择"是"
    • Next.js 会安装 Tailwind 4 版本

目录结构选项

  • Source 目录嵌入:
    • 询问是否将整个应用嵌入 src 目录
    • 适合 monorepo 项目
    • 个人偏好:选择"否"

路由系统选择

  • App Router:
    • 必须选择"是"
    • 不要使用 Pages Router(除非维护 legacy 项目)
    • Next.js 13 的 App Router 虽然口碑不佳,但 Next.js 15 版本已大幅改进

开发工具选项

  • Turbopack:

    • 用 Rust 重写的 Webpack 替代品,速度更快
    • 建议选择"否"
    • 原因:
      • 与 Vercel 生产部署不兼容
      • 与许多工具的兼容性问题
      • 虽然开发模式下性能不错
  • TypeScript 别名: 根据个人需求选择

项目初始化后的配置

Prettier 配置(可选但推荐)

创建 .prettierrc 文件:

{
  "semi": false,
  "singleQuote": true
}

个人偏好设置:

  • "semi": false - 禁用分号
  • "singleQuote": true - 使用单引号

启动开发服务器

npm run dev

默认配置:

  • 默认端口:3000
  • 如果 3000 端口被占用,会自动选择新端口

默认项目结构

生成的核心文件

  • 单个首页(index 页面)
  • 页面布局文件(layout)
  • 默认 CSS 样式(支持深色主题)
  • Tailwind 配置(如果选择安装)

项目特点

  • 配置方面:完整的工具链配置(Tailwind、ESLint 等)
  • Next.js 方面:仅 3 个核心文件,保持简洁
  • 在功能性和简洁性之间取得良好平衡

学习建议

  • 这是开始 Next.js 开发的最佳方式
  • 配置完整但不过度复杂
  • 为后续开发提供了良好的基础架构

3-project-structure-config

概述

NextJS 是一个高度规范化的框架,它使用文件系统来实现这些规范。不同的文件名和文件名中的不同字符可以确定不同的功能特性。

App Router vs Pages Router

App Router(推荐)

  • 从 NextJS 13 开始引入
  • 使用app目录
  • 提供更多灵活性和功能
  • 充分利用 Vercel 等平台的新功能
  • 建议:从头开始构建项目时始终使用 App 目录

Pages Router(不推荐)

  • NextJS 13 之前的方案
  • 使用pages目录
  • 语法和功能与 App Router 完全不同
  • 虽然更简单,但功能有限
  • 仍然受支持,但不建议在新项目中使用

NextJS 特殊目录

NextJS 只对三个目录有特殊处理:

1. App 目录

  • 使用 App Router 的主要应用目录
  • 包含应用的所有路由和页面

2. Public 目录

  • 存放需要在互联网上公开访问的静态资源
  • 包括:图片、字体、文件等
  • 这些文件可以直接被应用提供服务

3. API 目录

  • 用于创建 API 端点
  • 其他所有目录都可以随意组织

App 目录中的特殊文件名

核心文件类型

Page 文件

  • 文件名:page
  • 作用:定义路由页面
  • 特点:NextJS 有自己的路由器,无需手动配置路由
  • 使用方法:在 app 目录内的目录中创建 page 文件即可自动配置路由

Layout 文件

  • 文件名:layout
  • 作用:为路由提供布局组件
  • 功能:包装页面的 UI 组件,且永不改变
  • 本质:就是一个布局容器

辅助文件类型

Loading 文件

  • 文件名:loading
  • 作用:页面加载时显示的加载屏幕
  • 使用场景:在获取数据时显示,直到数据获取完成

Error 文件

  • 文件名:error
  • 作用:页面出现错误时显示的错误屏幕
  • 使用场景:处理页面抛出的异常

其他特殊文件

  • not-found:404 页面
  • template:模板文件
  • 认证相关页面(预览版功能)
  • 401 等状态码页面

API 路由配置

基本规则

  • 文件名:必须叫route(如:route.ts
  • 功能:创建 API 端点
  • 技术实现:使用 serverless 函数,每个路由都是一个被调用的函数

重要说明

  • 在现代 NextJS 开发中,内部应用通常不需要使用 API 路由
  • API 路由主要用于提供外部开发者 API
  • 内部数据获取有更高效的方式

常见项目结构模式

推荐的目录组织

app/
├── components/     # 组件目录
├── ui/            # UI组件目录
├── (route-groups)/ # 路由组(NextJS特定功能)
└── ...其他自定义目录

组织原则

  • 除了 NextJS 特殊目录外,其他目录可以随意组织
  • 遵循团队约定和个人偏好
  • 保持结构清晰和逻辑性

核心要点总结

  1. 目录重要性排序:App 目录 > API 目录 > Public 目录 > 其他自定义目录
  2. 路由配置:通过文件系统自动配置,无需手动设置
  3. 灵活性:NextJS 只管理特定目录和文件名,其余完全自由
  4. 最佳实践:始终使用 App Router,避免使用过时的 Pages Router

4-static-vs-dynamic-routes-and-layouts

静态路由 vs 动态路由

静态路由

定义和特点

  • 包含静态信息且不会改变的路由
  • NextJS 默认会预渲染这些路由并返回 HTML
  • 相当于索引静态网站,永不改变

创建方式示例

app/
└── settings/
    └── page.tsx    # 创建 /settings 静态路由
  • 路由始终是 /settings
  • 路由本身是静态的(数据可能是动态的,后续会讲到)

动态路由

定义和特点

  • 通常包含参数的路由
  • 路由路径可以根据参数变化

创建方式示例

app/
└── users/
    └── [id]/
        └── page.tsx    # 创建动态路由 /users/:id

注解方法

  • 在目录名中使用方括号 [] 包围参数名
  • 示例:[id] 文件夹用于用户 ID 参数
  • 必须在文件夹内放置 page 文件

高级路由模式

Catch All Routes(捕获所有路由)

基本语法

app/
└── docs/
    └── [...topic]/
        └── page.tsx    # 匹配 /docs 的任意子路径

特点

  • 使用三个点 ... 展开语法 + 参数名
  • 捕获任意嵌套深度的路径
  • 无论嵌套多深都会匹配

可选捕获语法

app/
└── docs/
    └── [[...topic]]/
        └── page.tsx    # 同时匹配 /docs 和任意子路径
  • 使用双方括号 [[...]]
  • 既匹配基础路径(/docs),也匹配所有子路径

实际应用场景

  • 内容型网站(如 Vercel 文档网站)
  • URL 结构:/docs/app + 各种子主题
  • 所有主题页面使用相同的页面布局
  • 只是内容不同,避免为每个主题手动创建路由
  • 结合预渲染和 getStaticParams 使用

Layout(布局)

基本概念

定义

  • 包装页面的组件,永不改变
  • NextJS 内置的 React 布局解决方案
  • 本质上就是你在 React 中使用布局的方式

根布局特点

  • 类似于 index.html
  • 职责是渲染其子组件(即路由)
  • 内容永不重新渲染

Layout 的优势

性能优化

  • 路由切换时布局不会重新渲染
  • 避免导航元素等公共组件的重复渲染
  • 防止性能损耗

适用场景

  • 导航元素
  • 全局样式设置
  • 任何在路由间应该保持不变的 UI 组件

Layout 的层次结构

布局继承规则

  • 每个路由查找最近的祖先布局
  • 必须有根布局(整个应用)
  • 布局可以嵌套继承

布局层次示例

app/
├── layout.tsx          # 根布局(必需)
├── users/
│   ├── layout.tsx      # users特定布局
│   └── [id]/
│       └── page.tsx
└── settings/
    ├── layout.tsx      # settings特定布局
    └── page.tsx
  • users 和 settings 的布局会嵌套在根布局内
  • 形成布局的嵌套结构

Template vs Layout

Template 特点

  • 与 Layout 完全相同,但有一个关键区别
  • 每次路由切换时都会重新渲染

使用场景

  • 布局中包含动画效果
  • 需要客户端状态在路由切换时重置
  • 任何需要在路由变化时更新的布局逻辑

使用方法

  • layout 文件名替换为 template

Route Groups(路由组)

基本概念

定义

  • 允许路由共享布局但不影响 URL 结构
  • 使用圆括号 () 创建

使用场景示例

需求场景

  • 应用包含营销页面(静态页面)和仪表板(实际应用)
  • 希望它们有不同的布局
  • 不希望 URL 反映这种分组

创建方式

app/
├── (marketing)/
│   ├── layout.tsx      # 营销页面布局
│   └── about/
│       └── page.tsx    # URL: /about (不是 /marketing/about)
└── (dashboard)/
    ├── layout.tsx      # 仪表板布局
    └── users/
        └── page.tsx

关键特点

  • 圆括号内的名称不会出现在 URL 中
  • 允许路由共享公共布局
  • 不改变 URL 结构

Route Groups 与 Root Layout 的关系

布局继承

  • Route Groups 中的布局仍然继承其他布局
  • 始终从最近的祖先继承

Root Layout 的可选性

  • 如果使用 Route Groups,可以不需要根目录的根布局
  • 可以在 Route Groups 中放置各自的布局
  • 每个路由会根据访问的路径使用相应的布局

首页处理

  • 可以将首页放在 Route Group 中
  • 由于 Route Group 不影响 URL,首页仍然是 /
  • 但如果有其他不在 Route Group 中的路由,会因缺少布局而出错

基本使用

重要原则

  • 不要使用 <a> 标签,使用 Link 组件

功能特点

  • 与 anchor 标签功能相同
  • 额外提供预取功能
  • 实现客户端路由

预取和客户端路由

  • 自动预取链接资源
  • 实现单页应用的路由切换体验
  • 类似于其他路由框架的 Link 组件

导入方式

  • 来自 NextJS:import Link from 'next/link'

5-pages-and-routing-exercise

项目路由结构需求

完整路由清单

根布局

  • 根布局(已存在,无需创建)

Auth 路由组

  • /signin
  • /signup

Marketing 路由组

  • /(首页)
  • 其他页面(FAQ、pricing 等,可选)

Dashboard 路由

  • /dashboard
  • 包含自己的布局

Issues 路由

  • /issues/new
  • /issues/:id/edit(动态路由)

创建路由的基本规则

组件导出规则

必须使用默认导出

  • 所有特殊文件(page、layout 等)必须使用默认导出
  • NextJS 通过默认导出识别要渲染的组件

组件返回值要求

  • 每个组件必须返回内容(可以是 null,但必须返回)
  • 如果不返回任何内容,应用会报错

文件命名规则对比

App 目录规则

  • 路由名称 = 目录名称
  • 文件名固定为:page.tsxlayout.tsx
  • 示例:signup/page.tsx 创建 /signup 路由

Pages 目录规则(旧版本)

  • 路由名称 = 文件名称
  • 示例:signup.tsx 创建 /signup 路由

实际创建过程

Marketing 路由组创建

目录结构

app/
└── (marketing)/
    ├── layout.tsx
    └── page.tsx    # 首页 /

Layout 组件示例

export default function MarketingLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return <div>{children}</div>;
}

关键要点

  • Layout 组件必须渲染 children
  • 如果不渲染 children,页面将只显示布局内容,看不到实际路由

Dashboard 路由创建

目录结构

app/
└── dashboard/
    ├── layout.tsx
    └── page.tsx    # /dashboard

特点

  • 有自己的独立布局
  • 布局会嵌套在根布局内

Auth 路由组创建

目录结构

app/
└── (auth)/
    ├── signin/
    │   └── page.tsx    # /signin
    └── signup/
        └── page.tsx    # /signup

路由组特点

  • 使用圆括号,不出现在 URL 中
  • 访问路径是 /signin,不是 /auth/signin

Issues 路由创建

目录结构

app/
└── issues/
    ├── new/
    │   └── page.tsx        # /issues/new
    └── [id]/
        └── edit/
            └── page.tsx    # /issues/:id/edit

动态路由特点

  • 使用方括号 [id] 创建动态参数
  • 可以访问任意 ID,如:/issues/123/edit

热模块替换特性

开发体验优势

自动重载

  • 添加新路由无需重启服务器
  • NextJS 内置热模块替换功能
  • 甚至环境变量文件更改也会自动识别

Layout 嵌套机制

嵌套规则

实际演示结果

  • 根布局包含营销布局
  • 营销布局包含实际页面内容
  • 形成:根布局 → 营销布局 → 页面内容

路由冲突问题

冲突场景

  • 在根目录和路由组中同时放置 page.tsx
  • 会产生路由冲突,NextJS 选择最近的路由

避免冲突方法

  • 仔细规划路由结构
  • 确保同一 URL 路径只有一个 page 文件
  • 路由组内的页面不与根目录页面冲突

项目组织建议

目录结构选择

组件位置灵活性

  • components 目录可以放在 app 目录外
  • app 目录专门用于路由相关文件
  • API 目录必须在 app 目录内

根布局使用建议

  • 始终保持根布局用于全局设置
  • 根布局包含所有应用共同的基础设置(字体、标题等)
  • 具体功能区域使用各自的子布局

TypeScript 类型处理

实践态度

  • 对框架提供的 props(如 children)可以忽略类型错误
  • 对自己创建的组件和函数应该严格添加类型
  • 可以使用 React 提供的类型,如 React.ReactNode

6-styling-marketing-auth-pages

NextJS 中的样式方案概述

基本原理

核心观点

  • NextJS 中有无限种样式方案
  • 因为 NextJS 就是 React,而 React 有无限种样式方案
  • 重点是构建营销页面和认证页面(签出体验)

课程重点

  • 不会手写大量 CSS 和 HTML 结构
  • 通过复制粘贴完成样式部分
  • 重点关注 NextJS 相关的预渲染和静态页面概念

CSS Modules

基本概念

定义和作用

  • 将 CSS 制作成模块
  • 核心优势:CSS 作用域隔离,避免全局冲突
  • 为样式创建随机类名值,防止样式冲突
  • 只加载组件特定的 CSS,不会加载全部 CSS

工作原理

  • CSS 与组件绑定,实现样式作用域
  • 避免全局样式污染
  • 提高 CSS 加载效率

实际使用示例

文件创建

  • 文件命名:newissues.module.css
  • 必须包含 .module. 标识

CSS 编写

.button {
  background-color: red;
}

组件中使用

import styles from "./newissues.module.css";

export default function NewPage() {
  return (
    <div>
      <button className={styles.button}>Hello</button>
    </div>
  );
}

特点

  • 导入的对象属性名对应 CSS 类名
  • 提供 TypeScript 类型支持
  • 浏览器中显示的类名是随机生成的

适用场景和限制

适用场景

  • 纯应用开发
  • 需要样式隔离的场景

不适用场景

  • 绝对不要在库开发中使用

Global CSS

基本概念

定义和用途

  • 全局样式文件
  • 适用于所有页面的通用样式

最佳使用场景

  • CSS 变量定义
  • Tailwind 配置
  • 字体设置
  • 主题配置
  • 在所有页面都需要的样式

使用方法

导入位置

  • 建议在根布局(RootLayout)中导入
  • 通过根布局的继承机制作用到所有页面

导入语法

import "./globals.css";

使用原则

  • 不要滥用全局 CSS
  • 不要把组件特定样式放在全局 CSS 中
  • 保持全局 CSS 文件精简
  • 使用其他方案处理组件样式

Tailwind CSS

推荐理由

强烈推荐使用

  • 在服务器端渲染中兼容性好
  • 可以使用现成的类名,无需自己编写

优势特点

开发效率

  • 节省大量开发时间
  • 与 React 服务器组件兼容性好
  • 使用预定义类名,无需自己创建

兼容性

  • 在各种环境中都能正常工作
  • 边缘计算环境
  • 服务器端
  • 客户端
  • 所有环境通用

组件库讨论

Shadcn/ui 评价

优点认可

  • 外观设计好
  • 开发效率高
  • AI 友好(AI 容易理解和使用)

个人观点和质疑

  • 困惑点:复制粘贴组件需要自己维护,失去了组件库的本质优势
  • 理解角度:从快速开发和完全定制的角度看有价值

适用场景

  • 需要完全控制组件定制
  • AI 辅助开发
  • 快速原型开发

个人选择

当前做法

  • 不使用任何组件库
  • 只使用 Tailwind
  • 需要功能性组件时使用无样式的 headless 组件(如 Ark)

Material UI 评价

历史看法

  • 十年前刚出现时很喜欢
  • 现在认为太臃肿

缺点

  • 过于企业化
  • 太有主见性
  • 大量定制后失去 Material 特色
  • 适合想要 Android 应用外观的场景

CSS-in-JS 方案

基本介绍

常见库

  • Styled Components
  • 其他 CSS-in-JS 解决方案

不推荐使用的原因

服务器组件兼容性问题

  • 在 React 服务器组件中不容易工作
  • 依赖库作者的正确实现

维护复杂性

  • 需要构建系统确保样式正确放置
  • 增加了不必要的复杂性

Styled-jsx

基本介绍

NextJS 内置方案

  • NextJS 提供的 CSS-in-JS 解决方案
  • 在 JSX 中使用 style 标签

使用方式

<style jsx>{`
  .button {
    background-color: red;
  }
`}</style>

评价和建议

适用场景限制

  • 特殊环境下可能有用(如邮件模板)
  • 没有完整构建系统的环境

CSS 预处理器

历史回顾

常见预处理器

  • Sass/SCSS
  • Less
  • Stylus(讲师曾经的最爱)

当前建议

不推荐新项目使用

  • PostCSS 已经替代了预处理器的功能

遗留项目理解

  • 对于遗留项目表示理解
  • 但新项目不建议选择预处理器

内联样式(Style 属性)

使用场景

可接受的情况

  • 客户端状态绑定到样式
  • 动态定位和坐标变化
  • 动画库的使用场景
  • React Native 开发(必需方式)

不推荐的用法

  • 绘制盒子和改变字体
  • 作为主要样式方案

设计原则

推荐做法

  • 使用 Tailwind 的扩展机制创建自定义样式
  • 在 CSS 文件中定义自定义 Tailwind 主题
  • 保证跨框架兼容性

优势

  • 样式可以在任何框架中使用
  • 保证一致的外观和行为

7-static-pages

静态页面概述

什么是静态页面

  • 定义:不获取数据或至少不获取任何动态数据的常规页面
  • 特点:不针对用户个性化,对所有人都相同
  • 优化:静态页面经过高度优化

静态页面识别

在 Next.js 开发模式下,可以通过页面右下角的 Next.js logo 查看页面信息:

  • 点击 logo 显示路由信息
  • 显示 "route static" 表示该页面为静态页面
  • 说明该路径将在构建时或数据重新验证后在后台预渲染

静态页面 vs 动态页面

静态页面场景

  • 静态博客文章
  • 产品页面
  • 营销页面(首页、定价页面等)
  • 任何在构建时已知且不会改变的内容

动态页面触发条件

以下情况会使页面变为动态页面:

  • 使用动态参数(如 [slug])并在页面中使用该参数
  • 访问 cookies
  • 显式声明为动态页面
  • 使用 suspense
  • 包含个性化内容

客户端组件的使用

'use client' 指令

"use client";
  • 这是 React 功能,不是 Next.js 特有的
  • 用于标识传统的单页应用客户端 React 组件
  • 必须显式声明才能使用客户端功能

实际应用场景:时间戳组件

  • 问题:在静态页面中显示当前年份的动态值
  • 解决方案:使用 'use client' 指令,在客户端执行动态计算
  • 原因:静态页面会被缓存,动态值每次执行结果不同

页面编译和渲染机制

开发模式下的编译

  • 页面只在首次访问时才会被 Next.js 编译
  • 首次编译耗时较长(如示例中的 336 毫秒)
  • 后续访问直接使用缓存,无需重新编译
  • 文件更改时触发热模块重载和重新编译

Turbopack 优势

  • 显著提升编译速度
  • 特别是在首次编译步骤上效果明显
  • 但目前兼容性问题仍需解决

基本用法

import Link from "next/link";

<Link href="/pricing">定价</Link>;

核心特性

  • 必须包含 href 属性
  • 类似锚标签的使用方式
  • 自动进行客户端路由
  • 默认启用预取功能

预取控制

<Link href="/pricing" prefetch={false}>
  定价
</Link>

静态页面预渲染 (getStaticParams)

使用场景

  • 博客文章页面
  • 文档页面
  • 任何需要根据外部数据源生成多个静态页面的场景

工作原理

export async function generateStaticParams() {
  // 获取所有博客文章
  const posts = await fetchAllBlogPosts();

  // 返回匹配动态路由的参数数组
  return posts.map((post) => ({
    slug: post.slug,
  }));
}

实现步骤

  1. 导出 generateStaticParams 函数
  2. 从外部数据源获取所有数据
  3. 返回包含动态路由参数的对象数组
  4. Next.js 为每个参数生成对应的静态页面

增量静态再生 (ISR)

概念

  • 解决大量静态页面构建时间过长的问题
  • 按需生成静态页面,而不是预先生成所有页面

工作机制

  1. generateStaticParams 中只返回最重要的页面(如最新的 50 篇文章)
  2. 当用户访问未预渲染的页面时,服务器端实时渲染
  3. 渲染完成后页面变为静态页面,后续访问直接使用缓存

优势

  • 减少构建时间
  • 避免编译无人访问的旧内容
  • 平衡性能和构建效率

文件组织最佳实践

特殊文件名

避免使用以下特殊文件名(除非有特定用途):

  • page.tsx
  • layout.tsx
  • template.tsx
  • loading.tsx
  • error.tsx
  • 404.tsx
  • not-found.tsx

组件组织

  • 可以在路由目录下放置其他组件文件
  • 只要不使用特殊文件名,Next.js 不会将其视为路由文件
  • 建议:生产环境中应该合理规划组件结构,避免所有代码都放在一个文件中

性能考虑

构建时间优化

  • 大量静态页面会显著增加构建时间
  • 可能出现 30-40 分钟的部署时间
  • 使用增量静态再生避免编译不必要的内容

开发体验

  • 首次访问页面时的编译延迟是正常现象
  • 包含数据获取的复杂页面编译时间会更长
  • Turbopack 可以显著改善这种体验
Next.js Fundamentals, v4