📚 全栈开发学习系列
阶段一:编程基础 ✅ 已完成
01-08 路线总览 → Python → C → 数据结构 → Git → Linux
阶段二:Web 全栈 ✅ 已完成
09-16 HTML/CSS → JavaScript → HTTP → FastAPI → PostgreSQL → 认证授权 → 综合实战
阶段三:前端深化
✓ 17 React 入门:组件化思想与 JSX 语法
✓ 18 React 进阶:状态管理与副作用钩子
✓ 19 TypeScript 入门:类型系统与类型注解
20 Next.js 基础:SSR 与路由体系(当前篇)
21 Tailwind CSS 基础
22 前端工程化(Vite/Webpack)
23 前端测试与 API 测试
阶段四至五:跨平台 App 与部署运维(后续持续更新)
Next.js 基础:SSR 与路由体系
进阶
Next.js 15.5
React 19
读完本篇你将能:理解 Next.js 的全栈框架定位与核心渲染策略,掌握 App Router 文件路由体系,区分 Server Components 与 Client Components 的使用场景,创建动态路由并选择合适的数据获取方式。
📑 本文目录
01Next.js 是什么:React 全栈框架的定位
02创建项目:从零搭建 Next.js 应用
03App Router:文件即路由的核心体系
04布局系统:layout 与嵌套结构
05Server Components 与 Client Components
06渲染策略:SSR、SSG、ISR 与 CSR 对比
07动态路由与 Route Handlers
08动手练习
01 Next.js 是什么:React 全栈框架的定位
前两篇你已经掌握了 React 组件化开发和 TypeScript 类型系统。但用纯 React 构建的应用有一个先天限制:它是客户端渲染(CSR)的单页应用——浏览器先下载空 HTML 骨架,再执行 JavaScript 生成页面内容。这导致首屏白屏时间长、SEO 搜索引擎抓不到内容、社交媒体分享无法生成预览卡片。
Next.js 就是为解决这些问题而生的 React 全栈框架。它在 React 之上增加了服务端渲染能力、文件路由系统、API 接口层和构建优化,让你无需从零配置 Webpack 或 Vite,开箱即得一个可部署到生产环境的完整 Web 应用。当前最新稳定版本是 Next.js 15.5.23 LTS(2026 年 8 月发布),内置 Turbopack 构建器,基于 React 19。
纯 React SPA vs Next.js
| 维度 |
纯 React (Vite + React) |
Next.js 15 |
| 渲染方式 |
仅 CSR(客户端渲染) |
SSR + SSG + ISR + CSR 可选 |
| 路由系统 |
需安装 react-router 手动配置 |
App Router 文件即路由,零配置 |
| SEO |
差(首屏空 HTML) |
好(服务端生成完整 HTML) |
| API 接口 |
需单独搭建后端服务 |
内置 Route Handlers,前后端同仓 |
| 构建工具 |
Vite |
Turbopack(默认)/ Webpack |
| 适用场景 |
后台管理系统、内部工具 |
博客、电商、企业官网、全栈应用 |
一个关键区别:纯 React 应用在浏览器中运行,用户访问时服务器返回的 HTML 几乎是空的,只有一个 <div id="root"></div> 和一个巨大的 JS bundle。而 Next.js 在服务器上就把 React 组件渲染成完整 HTML,用户收到的首屏已经有实际内容——这缩短了首屏可见时间,也让搜索引擎爬虫能直接读到页面内容。
💡 小贴士:Next.js 不替代 React
Next.js 是 React 的"扩展层"而非替代品。你写的仍然是 React 组件,用熟悉的 JSX 语法和 Hooks API。Next.js 在此基础上提供了路由约定、服务端渲染入口和构建优化——你可以理解为"React 是引擎,Next.js 是整车"。掌握了前两篇的 React 知识,迁移到 Next.js 的学习曲线非常平缓。
02 创建项目:从零搭建 Next.js 应用
理解了 Next.js 的定位后,让我们动手创建一个真实项目。创建过程只需要一条命令,Next.js 的脚手架工具会交互式地询问你的配置偏好。
用 create-next-app 初始化
在终端执行以下命令(需要 Node.js 20+ 环境):
Bash
npx create-next-app@latest my-next-app
脚手架会依次询问以下问题,这里给出推荐选择:
交互式提示
|
1
2
3
4
5
6
7
|
Would you like to use TypeScript? » Yes
Would you like to use ESLint? » Yes
Would you like to use Tailwind CSS? » Yes
Would you like your code inside a `src/` directory? » Yes
Would you like to use App Router? » Yes
Would you like to use Turbopack? » Yes
Would you like to customize the import alias? » No
|
全部选完后,脚手架自动安装依赖。完成后进入目录并启动开发服务器:
Bash
|
1
2
|
cd my-next-app
npm run dev
|
终端输出 Local: http://localhost:3000 表示启动成功。浏览器打开该地址,你会看到 Next.js 默认欢迎页面。
项目目录结构
创建完成后,项目结构如下(仅列出关键文件):
目录结构
|
1
2
3
4
5
6
7
8
9
10
11
|
my-next-app/
├── src/
│ └── app/
│ ├── layout.tsx # 根布局
│ ├── page.tsx # 首页 /
│ └── globals.css # 全局样式
├── public/ # 静态资源
├── next.config.ts # Next.js 配置
├── tsconfig.json # TypeScript 配置
└── package.json
|
src/app/ 目录是 App Router 的根——这里面的每个文件夹对应一个 URL 路径段,每个 page.tsx 文件就是一个可访问的页面。下一节我们会详细讲解这个路由体系。
⚠️ 常见错误:Node.js 版本过低
Node 16 启动项目报错 — Next.js 15 要求 Node.js 20+
✓ 正确:先执行 node -v 确认版本,低于 20 用 nvm 升级:nvm install 20 && nvm use 20
03 App Router:文件即路由的核心体系
项目跑起来了,现在来理解 Next.js 最核心的设计——App Router 路由体系。它的核心原则是"文件即路由":你在 src/app/ 下创建什么目录结构,浏览器就有什么 URL 路径。不需要像 react-router 那样手动编写路由表。
路由约定规则
•
文件夹定义 URL 路径段:app/about/page.tsx 对应 /about
•
page.tsx 是唯一让路由可访问的文件——没有 page.tsx 的文件夹不构成路由
•
嵌套文件夹 = 嵌套路由:app/blog/first-post/page.tsx 对应 /blog/first-post
•
用方括号定义动态路由:app/blog/[slug]/page.tsx 匹配 /blog/hello-world 等任意路径
创建多个页面:实战演示
在 src/app/ 下创建 about/page.tsx 和 blog/page.tsx:
src/app/about/page.tsx
|
1
2
3
4
5
6
7
|
export default function AboutPage() {
return (
<main>
<h1>关于我们</h1>
<p>这是一个用 Next.js 构建的网站。</p>
</main>
);
}
|
保存后直接访问 http://localhost:3000/about,页面立即生效——无需重启服务器,Turbopack 的热更新会自动编译新文件。
以下是文件结构与 URL 的对应关系:
路由映射
|
1
2
3
4
5
6
|
src/app/page.tsx # → /
src/app/about/page.tsx # → /about
src/app/blog/page.tsx # → /blog
src/app/blog/[slug]/page.tsx # → /blog/:slug
src/app/settings/profile/page.tsx # → /settings/profile
src/app/settings/page.tsx # → /settings
|
页面间导航:Link 组件
Next.js 提供了 Link 组件实现客户端导航——点击后不会重新加载整个页面,而是只获取目标路由的数据和组件,实现页面切换的瞬时响应。
src/app/page.tsx
|
1
2
3
4
5
6
7
8
9
10
|
import { Link } from "next/link";
export default function HomePage() {
return (
<nav>
<Link href="/">首页</Link>
<Link href="/about">关于</Link>
</nav>
);
}
|
第 1 行从 next/link 导入 Link 组件,用 href 属性指定目标路径。点击 Link 后,Next.js 会预取目标路由的 JS chunk,在用户点击时几乎零延迟地切换页面,而不是重新请求整个 HTML。
💡 小贴士:Link 预取行为
Next.js 的 Link 组件在进入视口时会自动预取目标路由的代码。这意味着用户还没点击链接,目标页面的 JS 已经下载好了。在移动端弱网环境下,这个机制可能消耗额外流量。可以通过 <Link href="/about" prefetch={false}> 关闭预取,或用 prefetch={"intent"} 仅在悬停时预取。
04 布局系统:layout 与嵌套结构
App Router 的路由体系让你能轻松创建页面,但一个真实网站通常有统一的导航栏、页脚和全局样式。如果每个页面都重复写这些,维护成本很高。Next.js 用 layout.tsx 解决这个问题——它是包裹在所有子路由外面的公共布局,切换页面时不会重新渲染。
根布局 layout.tsx
每个 Next.js 项目都有一个根布局 src/app/layout.tsx,它包裹整个应用,必须包含 <html> 和 <body> 标签:
src/app/layout.tsx
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import type { Metadata } from "next";
import "./globals.css";
export const metadata: Metadata = {
title: "我的 Next.js 应用",
description: "用 Next.js 15 构建的全栈应用",
};
export default function RootLayout({
children,
}: { children: React.ReactNode }) {
return (
<html lang="zh-CN">
<body>{children}</body>
</html>
);
}
|
children 是 React 的特殊 props——Next.js 会把当前路由匹配到的 page.tsx 内容作为 children 传入 layout。切换页面时,layout 不重新渲染,只有 children 部分更新。这类似于把导航栏和页脚写在 layout 里,所有子路由自动共享。
嵌套布局
布局可以嵌套。比如 /blog 下的所有页面共享一个博客侧边栏,你可以在 src/app/blog/layout.tsx 中定义:
src/app/blog/layout.tsx
|
1
2
3
4
5
6
7
8
|
export default function BlogLayout({
children,
}: { children: React.ReactNode }) {
return (
<div>
<aside>博客分类导航...</aside>
<main>{children}</main>
</div>
);
}
|
访问 /blog 时,渲染顺序是 RootLayout → BlogLayout → BlogPage,三层嵌套。访问 /about 时,只渲染 RootLayout → AboutPage,不经过 BlogLayout。
特殊文件:loading、error、not-found
除了 page.tsx 和 layout.tsx,App Router 还定义了几个特殊文件名来处理 UI 状态:
| 文件名 |
作用 |
触发时机 |
loading.tsx |
加载状态骨架屏 |
页面数据获取中 |
error.tsx |
错误捕获界面 |
页面渲染抛出异常 |
not-found.tsx |
404 页面 |
路由未匹配 |
template.tsx |
类似 layout 但每次导航都重新挂载 |
需要重置状态的路由切换 |
其中 loading.tsx 配合 React 19 的 Suspense 机制工作——当页面组件在服务端获取数据时,用户会先看到 loading.tsx 的内容,数据就绪后自动替换为实际页面。这让服务端数据获取不再是"白屏等待"的体验。
05 Server Components 与 Client Components
掌握了路由和布局后,现在来理解 Next.js 最重要也最容易混淆的概念——Server Components 和 Client Components。在纯 React 中,所有组件都在浏览器中运行;而 Next.js 把组件分成了两半:一部分在服务器上执行,一部分在浏览器中执行。理解这条边界线是用好 Next.js 的关键。
默认是 Server Component
App Router 中所有组件默认是 Server Component——它们在服务器上渲染,输出的是 HTML 字符串,不包含任何 JavaScript。这意味着:
•
不能使用 useState、useEffect 等 Hooks(它们依赖浏览器运行时)
•
不能使用事件监听(onClick、onChange 等)
•
可以直接使用 async/await 获取数据库或 API 数据
•
不打包进客户端 JS bundle,页面加载更快
一个典型的 Server Component 直接用 async 函数获取数据:
src/app/blog/page.tsx (Server Component)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
interface Post {
id: number;
title: string;
}
export default async function BlogPage() {
const res = await fetch("https://api.example.com/posts");
const posts: Post[] = await res.json();
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
|
注意第 6 行的 async 关键字——Server Component 可以是异步函数,直接用 await 获取数据,不需要 useEffect 或状态管理。数据获取和渲染在服务端一次性完成,零客户端 JS 开销。
"use client" 指令:切换为 Client Component
当组件需要交互(状态、事件、浏览器 API)时,在文件顶部添加 "use client" 指令,将其声明为 Client Component:
src/app/components/Counter.tsx (Client Component)
|
1
2
3
4
5
6
7
8
9
10
11
12
|
"use client"
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
点击次数:{count}
</button>
);
}
|
第 1 行的 "use client" 是一个字符串指令,不是 import 语句。它告诉 Next.js 这个文件及其导入的模块都要打包进客户端 JS。一旦声明为 Client Component,就可以使用所有 React Hooks 和事件处理了。
⚠️ 常见错误:Server Component 中使用 Hooks
在 page.tsx 中直接写 useState — Server Component 不支持 Hooks
✓ 正确:把需要状态的交互逻辑拆到独立组件中,在组件顶部加 "use client",再从 Server Component 中引入
💡 小贴士:组件边界判断法
判断一个组件应该是 Server 还是 Client,问自己一个问题:"这个组件需要在用户交互后改变吗?"如果答案是"不需要,它只展示数据"——Server Component。如果"需要,它有按钮点击、输入框、下拉刷新"——Client Component。实践中的最佳模式是:Server Component 获取数据,把数据作为 props 传给 Client Component 处理交互。这样既享受了服务端渲染的 SEO 和首屏优势,又保留了客户端交互能力。
06 渲染策略:SSR、SSG、ISR 与 CSR 对比
理解了组件的 Server/Client 区分后,我们来横向对比 Next.js 支持的四种渲染策略。选择正确的策略直接影响页面性能和用户体验。
| 策略 |
全称 |
何时生成 HTML |
适用场景 |
| SSR |
Server-Side Rendering |
每次请求时 |
个性化页面、实时数据 |
| SSG |
Static Site Generation |
构建时生成一次 |
博客、文档站、营销页 |
| ISR |
Incremental Static Regeneration |
构建时 + 定时更新 |
电商商品页、新闻列表 |
| CSR |
Client-Side Rendering |
浏览器中动态生成 |
后台管理、需要登录的仪表盘 |
在 App Router 中,渲染策略通过 fetch 函数的 cache 和 revalidate 选项控制:
渲染策略控制
|
1
2
3
4
5
6
7
8
9
|
// SSG:构建时获取,永久缓存
const res = fetch(url, { cache: "force-cache" });
// SSR:每次请求都重新获取
const res = fetch(url, { cache: "no-store" });
// ISR:缓存 60 秒后自动重新生成
const res = fetch(url, { next: { revalidate: 60 } });
// CSR:客户端获取(在 Client Component 中)
"use client"; const [data] = useState(null);
|
App Router 默认使用 SSG——不带任何 cache 选项的 fetch 会被缓存,构建时生成静态 HTML。这让大多数页面在不牺牲实时性的前提下获得 CDN 级别的访问速度。只有需要实时数据的页面才需要显式声明 cache: "no-store" 或 revalidate。
构建时 SSG
→
定时 ISR
→
请求时 SSR
从左到右:实时性递增,性能递减。选择策略时在"内容更新频率"和"服务器负载"之间找到平衡点。
07 动态路由与 Route Handlers
前面我们创建的都是静态路由(/about、/blog)。但真实应用中,每个博客文章有自己的 URL(/blog/hello-world),每个商品有自己的页面(/product/42)。这就是动态路由——用方括号语法 [参数名] 匹配 URL 中的变量段。
动态路由参数
创建 src/app/blog/[slug]/page.tsx,通过 params props 获取 URL 中的 slug 值:
src/app/blog/[slug]/page.tsx
|
1
2
3
4
5
6
7
8
9
10
11
|
export default async function BlogPostPage({
params,
}: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const res = await fetch(`https://api.example.com/posts/${slug}`);
const post = await res.json();
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
|
注意 Next.js 15 中 params 是一个 Promise——需要用 await 解包(第 4 行)。访问 /blog/hello-world 时,slug 的值就是 "hello-world"。
generateStaticParams:预生成动态路由
动态路由默认按需渲染——用户访问时才获取数据。如果你想在构建时就把所有文章页面预生成静态 HTML(提升首次访问速度),使用 generateStaticParams:
generateStaticParams
|
1
2
3
4
5
6
7
|
export async function
export async function generateStaticParams() {
const posts = await fetch("https://api.example.com/posts");
const data = await posts.json();
return data.map((post) => ({ slug: post.slug }));
}
// 构建时为每个 slug 生成静态 HTML
|
Route Handlers:构建 API 接口
Next.js 不只是前端框架——它还能写后端 API。在 app/ 下创建 route.ts 文件即可定义 API 端点。这让你在一个项目里同时写前端和后端:
src/app/api/posts/route.ts
|
1
2
3
4
5
6
7
8
9
10
|
import
import { NextResponse } from "next/server";
export async function GET() {
const posts = [
{ id: 1, title: "第一篇" },
{ id: 2, title: "第二篇" },
];
return NextResponse.json(posts);
}
// GET /api/posts → 返回 JSON 数组
|
导出 GET、POST、PUT、DELETE 函数即对应不同 HTTP 方法。Route Handlers 运行在服务端,可以连接数据库、调用外部 API,替代你在阶段二学过的 FastAPI 接口——但如果你已经有了独立后端,Next.js 也可以纯粹作为前端调用外部 API。
💡 小贴士:generateStaticParams 与 ISR 配合
generateStaticParams 在构建时预生成所有已知的动态路由页面。但如果之后新增了一篇文章呢?配合 revalidate: 60(ISR),新文章在首次访问时会按需生成并缓存 60 秒,之后自动更新。这种"静态预生成 + 按需增量"的组合,是电商和博客类应用的最佳实践。
✏️ 动手练习
🟢 基础验证
创建一个新的 Next.js 项目,添加 /about 和 /contact 两个页面,在首页用 Link 组件实现到这两个页面的导航。验证:点击导航不刷新整个页面,浏览器 URL 正确变化。
🟡 组合应用
创建一个博客列表页 /blog(Server Component,用 fetch 获取数据)和文章详情页 /blog/[slug]。在列表页为每篇文章添加 Link 跳转到详情页。再创建一个 Client Component 的"点赞按钮"组件,嵌入到详情页中。提示:可以用 jsonplaceholder.typicode.com/posts 作为测试 API。
🔴 开放挑战
在练习项目的基础上添加 Route Handler API(/api/posts),返回模拟的文章列表 JSON。然后修改博客列表页,从自己的 API 获取数据而非外部 API。进阶:尝试用 revalidate: 60 配置 ISR,观察修改 API 返回数据后页面内容的变化时机。
📖 知识回顾
Next.js 定位
create-next-app
App Router
文件即路由
layout 嵌套
Server Components
use client
SSR/SSG/ISR
动态路由
Route Handlers
Turbopack
generateStaticParams
下篇预告
21 Tailwind CSS 基础:原子化样式开发
将学习 Tailwind CSS 的原子化设计理念、实用类系统、响应式布局和暗色模式,以及如何在 Next.js 中集成 Tailwind 实现高效样式开发