ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Next.js 从入门到实战:SSR/SSG/ISR 渲染模式与全栈开发指南

Next.js 从入门到实战:SSR/SSG/ISR 渲染模式与全栈开发指南 1. 从零开始Next.js 到底解决了什么问题1.1 一个真实的场景从 Vite 到 Next.js先讲一件我自己经历过的事。早两年我用 Vite 写了一个内容展示型的小站点开发体验确实舒服冷启动快、热更新跟手一套组合拳下来前端开发效率非常高。但是等这个项目真正上线后问题就接踵而来首屏白屏时间偏长、SEO 抓不到正文内容、分享链接到微信里打开只有一片空白。后来我不得不花大量时间在项目里补预渲染脚本、SSR 服务、路由守卫工作量几乎翻了一倍。这时候我才意识到Vite 和 Next.js 并不是一个层面的选择。Vite 是一个构建工具它解决的是开发体验和打包效率而 Next.js 是一个全栈框架它帮助你同时解决渲染、路由、数据获取、接口能力和部署问题。很多新手一上来就在“Next.js 和 Vite 哪个好”里面纠结其实这就像拿“发动机”和“整车”做比较——前提完全不同。如果你只需要做一个纯前端、强交互、弱 SEO 的管理后台Vite 完全够用但如果你要做面向用户的内容站、电商页面、官网或者需要服务端参与数据处理的业务Next.js 能省下你无数自己搭轮子的时间。这篇文章就是围绕 Next.js 从零基础到项目实战展开的。我不打算把它写成官方文档式的教程而是把我在实际项目里一步步走过来时遇到的关键问题、判断逻辑、踩过的坑全部分享出来。你不需要预先非常精通 React只要会一点组件、状态的基础概念就能跟完全程。1.2 核心渲染模式SSR、SSG、ISR、CSR 到底怎么选很多初学者走进 Next.js 里最先蒙圈的就是那一堆缩写SSR、SSG、ISR、CSR。这四个词决定了你的网页内容是在哪个环节生成的也是项目性能优化的分水岭。CSR客户端渲染浏览器加载 JS 后动态渲染内容。适合后台、仪表盘这类不需要 SEO 的强交互页面。在 Next.js 里如果你在客户端组件里用 useEffect 请求数据本质上就是 CSR。SSR服务端渲染每次用户请求页面时服务器实时渲染 HTML 返回。首屏内容可见SEO 好但服务器压力大。适合需要实时数据的页面比如带用户个性化信息的页面。SSG静态生成构建时一次性生成 HTML部署后全球 CDN 直接分发访问速度最快、服务器成本最低。适合博客、文档、营销页等变化不频繁的内容。ISR增量静态生成在 SSG 基础上增加“重新验证”机制每隔一段时间后台重新生成页面。适合内容会定期更新的站点比如新闻列表、商品列表。我当时带团队做项目时有一个简单有效的判断顺序内容是否因人而异是则 SSR内容更新频率高不高不高则 SSG处于中间态则 ISR完全不需要 SEO 且强交互则 CSR。这个决策逻辑在 Next.js 里恰好对应到不同的 API比如 App Router 下的generateStaticParams、revalidate配置项Pages Router 下的getStaticProps、getServerSideProps。理解了这层逻辑后面学习具体写法会轻松很多。1.3 什么时候不要用 Next.js说句实话Next.js 不是银弹。如果你的项目只是一个内部运营后台、一个纯展示的静态官网且没有动态数据或者团队前端基础薄、没有服务端运维经验那么用 Next.js 反而会带来多余的心智负担。这时候 Vite React 甚至 Vite Vue 或许是更务实的选择。我自己接项目时会做一个快速评估有没有动态内容要不要 SEO是否需要服务端能力三个问题只要有两个答案是“是”我才会考虑 Next.js。这个判断标准帮我避免了不少项目过度设计的问题。选型本身没有对错只有适配。2. 环境准备与项目初始化第一步踩稳2.1 Node 版本和包管理器正式开始之前先把环境收拾利索。我用的是 Node.js 20 LTS 版本Next.js 15 官方也要求 Node 18.18 以上。如果你还在用 Node 16 或更早版本大概率会在安装依赖时遇到各种奇怪的错误——那些错误提示不会直接告诉你是版本太老只会报一串“engine”警告或者安装到一半失败。包管理器方面npm、yarn、pnpm 都能用我建议新项目直接用 pnpm。原因是 Next.js 项目依赖数量非常大pnpm 的硬链接机制能大幅节省磁盘空间安装速度也快。实际对比过同一项目用 npm 安装耗时接近 40 秒用 pnpm 冷安装只要 20 秒左右热更新阶段差距更明显。当然如果你团队统一用 npm没有必要为了这点差异强行切换保持一致更重要。环境准备这一步还可以顺带装一个nvm或者fnm方便在多个 Node 版本之间切换。因为工作里你总会同时维护好几个项目有的是 Next.js 14、有的已经升到 Next.js 15各自对 Node 版本的要求有细微差别用版本管理工具能省去很多“刚换环境就跑不起来”的麻烦。2.2 create-next-app 实战和目录解读初始化项目最省事的方式当然是官方脚手架pnpm create next-applatest my-next-app执行过程中会有一些交互选项我按实际推荐的选择给你TypeScript选 Yes。别犹豫虽然刚开始写 TypeScript 会慢一点但项目规模一大类型系统能帮你减少非常多低级错误。ESLint选 Yes。代码规范从第一天就建立起来后面合代码时不至于鸡飞狗跳。Tailwind CSS看个人习惯。如果只想专注 Next.js 核心能力可以先不选用传统 CSS 也可以跑通全部课程。App Router / Pages Router选 App Router。这是新方向Next.js 后续版本只会持续增强 App Router。Turbopack可以选上。它的构建速度明显比 webpack 快尤其在你项目膨胀以后体验差距会更明显。项目生成后你会看到目录里多了很多文件和文件夹。新手拿到目录第一反应往往是“怎么这么多东西”。我带你过一遍最核心的几个app/ layout.tsx # 全局布局包裹所有页面 page.tsx # 首页组件 globals.css # 全局样式 public/ # 静态资源 next.config.ts # Next.js 配置文件在 App Router 里app目录就是整个项目的灵魂。每一个文件夹对应一个路由文件夹里的page.tsx就是该路由的页面内容。比如app/about/page.tsx对应的就是/about这个地址。这个约定比传统 React 项目里的 react-router 要直观很多你不需要手动维护一份路由表文件放在哪里路由就是什么。Pages Router 是上一代的约定式路由它在pages目录下工作用法也很成熟现在大量存量项目还在用。如果你是全新起项目我建议直接学 App Router如果你接手的是老项目那就先了解一下pages/api和_app.tsx这类概念。两者短期内不会互相替代但新项目没有必要从旧模型开始。2.3 开发服务器的运行脚本初始化完成之后package.json里已经有几条现成的命令scripts: { dev: next dev, build: next build, start: next start, lint: next lint }pnpm dev启动开发服务器默认端口 3000。开发模式下 Next.js 会开启热更新你改了代码保存浏览器里的页面几乎瞬间更新这个体验和 Vite 差别不大都很流畅。而且 Next.js 15 里默认启用了 Turbopack开发服务器的启动速度和文件监听效率都比以前好了很多。构建和发布时的操作顺序有一点要搞清楚pnpm build会执行编译、SSG 生成、代码检查等一系列流程产物在.next目录然后pnpm start用来启动生产服务器端口默认也是 3000。很多人第一次部署时直接在服务器上跑pnpm dev这是不对的开发模式的性能和安全策略都不适合生产环境。正确姿势永远是build之后再start。3. 数据获取与路由体系框架的骨架3.1 App Router 里的路由约定与布局App Router 最让我喜欢的一点是路由和组件的组织方式非常清爽。你可以把多个页面共享的框架结构抽到layout.tsx里比如导航栏、页脚、侧边栏它们会在所有子页面中保持统一同时不会重复渲染。这比早期 React 项目里每个页面自己引入导航组件要优雅太多。举一个实际例子。我在app/layout.tsx里放了一个顶栏导航export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( html langzh-CN body header这里是全局导航/header main{children}/main footer这里是页脚/footer /body /html ); }这样一来所有app目录下的页面都会自动拥有这个导航和页脚不需要在每个页面里重复写。如果你想某个页面不显示导航也好办可以在那个页面定义自己的layout或者用Route Group把页面分组处理。Route Group 是 App Router 里一个很实用但容易被忽略的特性。举个例子app/(marketing)/about/page.tsx和app/(marketing)/pricing/page.tsx都归到(marketing)这个分组里URL 路径并不会包含(marketing)你却可以给这一组页面单独设置一套 layout。这个机制适合多业务线、多栏目结构的中大型站点比把所有页面堆在一个目录里清楚得多。3.2 Server Components 与数据请求App Router 里默认情况下组件都是 Server Components也就是说它们在服务器上渲染完成后才发给浏览器。这意味着你可以在组件里直接写异步代码去数据库或第三方 API 获取数据不需要再用useEffect、useState去手动管理请求生命周期。一个最直观的对比传统的 React 写法类似在 Vite 项目中的模式import { useEffect, useState } from react; export default function UserProfile() { const [user, setUser] useState(null); useEffect(() { fetch(/api/user) .then((res) res.json()) .then(setUser); }, []); return div{user ? user.name : 加载中...}/div; }Next.js App Router 的写法async function getUser() { const res await fetch(https://api.example.com/user); return res.json(); } export default async function UserProfile() { const user await getUser(); return div{user.name}/div; }第二种写法在代码整洁度和可读性上要强太多。没有 loading 状态、没有 effect 依赖数组、没有状态同步的烦恼数据直接在服务器上拿完再输出 HTML。对于首屏性能的提升尤其明显因为客户端不需要先请求一套 JS 再根据结果渲染内容而是直接拿到最终页面。fetch在 Server Components 里还内置了缓存和重新验证机制。比如你可以这样控制一条数据的缓存时间const res await fetch(https://api.example.com/posts, { next: { revalidate: 60 }, });意思是数据在 60 秒内会复用缓存超过 60 秒后后端重新请求一次。这其实就是 ISR 理念在数据层级别的实现比在页面级别配置revalidate常量要灵活得多。3.3 Route Handlers 创建 APINext.js 不仅是前端框架它还是一个轻量后端。App Router 中任何一个路由文件夹里都可以放一个route.ts文件来定义 API 端点。比如在app/api/user/route.ts里import { NextResponse } from next/server; export async function GET() { const data { name: 张三, role: admin }; return NextResponse.json(data); }启动项目后访问/api/user就能拿到 JSON 数据。这个功能对你来说意味着什么意味着一个项目里前后端可以一起写不需要单独起一个 Express 服务也不需要在 Vite 项目里额外挂一个 JSON Server。当然如果真的业务复杂Next.js 的 Route Handlers 也足够支撑中小型业务的绝大部分接口需求。Route Handlers 里可以使用request对象读取参数、处理 POST 请求、设置响应头能力和 Express 这类框架基本对齐。我做实战项目的时候会把简单的 CRUD 接口全部丢进app/api里部署时一个应用搞定全部省了 CORS、跨域调试的不少麻烦。4. 从零到一一个完整项目的实战拆解4.1 项目需求与结构设计光聊概念不够尽兴我拿一个实际做过的项目来拆解一遍。这是一个小型的博客内容站需求大概是这样的首页展示文章列表每篇文章有标题、摘要、创建时间。文章详情页展示正文内容。有一个后台接口支持新增文章。站点需要被搜索引擎收录首屏加载尽量快。这种需求用 Next.js 解决非常合适既有动态内容又要 SEO。整个项目我分成三个大的部分路由页面、数据层、API 接口。目录结构设计如下app/ layout.tsx page.tsx # 首页 posts/ [id]/page.tsx # 文章详情页动态路由 api/ posts/ route.ts # 处理 POST 请求新增文章 lib/ posts.ts # 数据存取逻辑 data/ posts.json # 先用 JSON 文件做存储方便演示为什么用 JSON 文件做数据存储而不是直接上数据库因为对一篇教程型的实战项目来说如果你一开始就引入 PostgreSQL 或 MySQL读者很可能卡在环境搭建上。先用 JSON 文件把整个逻辑跑通后期再换成数据库替换的部分非常少。这种“先简化、后扩展”的思路在项目原型的阶段非常重要。4.2 实现文章列表页和详情页首页的关键代码在app/page.tsximport { getAllPosts } from ./lib/posts; export default async function HomePage() { const posts await getAllPosts(); return ( div h1最新文章/h1 ul {posts.map((post) ( li key{post.id} a href{/posts/${post.id}}{post.title}/a span{post.date}/span /li ))} /ul /div ); }这个组件是服务端组件getAllPosts在服务器上读取 JSON 文件后直接渲染成 HTML 列表。你打开浏览器查看页面源代码时能看到完整的文章标题和内容。这对 SEO 是友好的。接下来是详情页。因为文章数量是动态的访问路径是/posts/1、/posts/2这样的动态路由所以用[id]文件夹加generateStaticParams来配合生成静态页面import { getAllPosts, getPostById } from ../../lib/posts; export async function generateStaticParams() { const posts await getAllPosts(); return posts.map((post) ({ id: String(post.id) })); } export default async function PostPage({ params, }: { params: { id: string }; }) { const post await getPostById(Number(params.id)); if (!post) return div文章不存在/div; return ( article h1{post.title}/h1 p{post.date}/p div{post.content}/div /article ); }generateStaticParams的作用是在构建阶段就把所有文章详情页生成好。这样部署上线后用户访问详情页时根本不需要等待服务器动态计算CDN 直接返回现成的 HTML。如果你有几百篇文章这个机制会让全站性能提升得非常明显。4.3 实现新增文章的 API新增文章的接口我用 Route Handler 来实现。在app/api/posts/route.ts中import { NextResponse, NextRequest } from next/server; import { addPost } from ../../../lib/posts; export async function POST(request: NextRequest) { const body await request.json(); const { title, content } body; if (!title || !content) { return NextResponse.json( { error: 标题和内容不能为空 }, { status: 400 } ); } const post addPost(title, content); return NextResponse.json(post, { status: 201 }); }这个接口会在data/posts.json里追加一篇文章并返回新文章对象。如果你要接数据库只需要把addPost函数里的实现替换成 SQLINSERT语句接口层的代码完全不用动。这种数据访问层封装的方式给后续扩展留了很干净的边界。API 写完后可以在本地用自带的/api/posts地址测试也可以用 curlcurl -X POST http://localhost:3000/api/posts \ -H Content-Type: application/json \ -d {title:新文章,content:这里是正文内容}成功后返回 201 和文章数据首页列表也会在下次访问时自动看到新文章。4.4 部署到服务器项目写完后总不能只在本地跑。Next.js 项目部署方式有几种我分别讲一下各自的适用场景。如果是个人项目或原型验证部署到 Vercel 是最省心的。你只需要把代码推到 GitHub 仓库在 Vercel 上导入项目它会自动识别 Next.js完成构建和环境变量配置然后分配给你一个 HTTPS 域名。整个过程大约十分钟而且自带 CDN 和自动部署代码每次推送到主分支都会同步更新线上环境。如果你的项目需要部署在自己的服务器上可以用 Docker 加 PM2 的方式。一个最简的 Dockerfile 长这样FROM node:20-alpine AS builder WORKDIR /app COPY package.json pnpm-lock.yaml ./ RUN pnpm install --frozen-lockfile COPY . . RUN pnpm build FROM node:20-alpine AS runner WORKDIR /app COPY --frombuilder /app/.next ./.next COPY --frombuilder /app/public ./public COPY --frombuilder /app/package.json ./ RUN pnpm install --production EXPOSE 3000 CMD [pnpm, start]这个方式适合你已经有云服务器、或者业务数据需要放在境内的场景。需要注意.next目录是构建产物不要直接复制源码和生产依赖之外的东西。同时生产环境记得配置好环境变量不要把密钥写进代码仓库。5. 踩坑记录那些文档里找不到的细节5.1 Hydration 不一致问题我第一次用 Next.js 写页面时遇到过这么一个问题本地开发一切正常部署到线上后偶尔会看到控制台报错Hydration failed because the initial UI does not match what was rendered on the server。这个问题的根源是服务器渲染出来的 HTML 结构和浏览器端组件首次渲染出来的结构不一致。最常见的触发原因是使用了时间、随机数之类的不确定值。比如你的组件里这样写p当前时间{new Date().toString()}/p服务器在生成 HTML 时记录的是服务器当前时间浏览器端 Hydration 时拿到的是用户本地时间两边不一致React 就会报错并重新渲染。解决思路分两手如果这个值只在客户端才需要就放到useEffect里或者在组件定义时加use client并用动态导入配合ssr: false如果值对 SEO 并不重要干脆统一用构建时间或者不渲染出来。这类问题在文档里通常只有一句警告但实际项目里出现的频率非常高尤其是接手老代码时看到的类似报错基本都是在数据层引入了不确定因素。5.2 内存与缓存问题Next.js 开发服务器有一个“特性”不太友好跑久了内存占用会明显上升尤其当你频繁切换页面、文件监听规模很大时。这通常与开发模式下的编译缓存有关。如果你在本地开发几天没重启发现内存占用持续飙高可以试试把.next目录删掉再重新pnpm dev。这一步能清理掉很多无效缓存。生产模式下如果你用了 ISR 重新验证机制可能会出现一个现象页面更新不够“及时”。明明后台已经改了数据前端访问还是旧内容。这多半是因为revalidate时间还没到或者 CDN 层有额外缓存。排查思路是先确认 Next.js 应用本身的响应头是否带有x-nextjs-cache标识再看 CDN 配置。调试阶段可以把revalidate设小一点甚至临时用force-dynamic禁用缓存确认功能正常后再恢复。5.3 依赖版本兼容问题这个坑我帮别人排查过很多次。现象是项目在一个人电脑上跑得好好的换到另一个人电脑上安装依赖或启动时就报错。比较典型的元凶是node_modules里的依赖版本不一致或 lockfile 没有提交到代码仓库。解决方法是把pnpm-lock.yaml或package-lock.json加入版本控制并且统一包管理器。如果同时用了 npm 和 pnpm很容易在切换中造成依赖结构混乱。另外要留意 Next.js 版本与 React 版本的匹配。官方对每个 Next.js 版本都有对应的 React 版本要求不能随意升级。升级大版本前最好查看官方升级指南跑一遍next/codemod工具它会自动处理多数语法迁移。5.4 Next.js 和 Vite 的最终选择建议写到这里我觉得有必要把“Next.js vs Vite”这个热门话题再拉出来说透。它不是单纯的技术优劣而是项目类型与需求的匹配度问题。我把自己的选择标准总结成一张表供你参考维度推荐 Next.js推荐 Vite项目类型内容站、官网、电商、SEO 敏感项目后台管理系统、工具型应用、纯前端展示数据获取需要服务端介入、SSR/ISR主要靠客户端请求 API复杂度需要路由、渲染、API 一体化解决更关注前端构建速度和开发体验部署希望一个应用同时覆盖前后端已有独立后端前端只需静态部署团队能力能接受框架约束和 Node 服务只想专注 React/Vue 组件开发拿我自己来举例做官网首屏优化时我会毫不犹豫选 Next.js因为 SEO 的收益是实打实的做内部运营后台时Vite 的轻和快对我来说更舒服完全没有必要引入一个全栈框架。你要是能把这个判断逻辑想清楚就不会再被各种框架之间的口水仗带偏。5.5 几个提升效率的小建议最后分享几个我在项目里长期使用的小技巧。第一多利用 Next.js 的Link组件做预加载。它的自动预取能力可以让用户鼠标悬停在链接上时提前加载目标页面的资源整个站点体验会顺畅很多这一操作在 Vite 的 SPA 项目里需要手动做路由懒加载才勉强达到类似效果。第二善用环境变量。NEXT_PUBLIC_前缀的环境变量会暴露到浏览器端非此前缀的只存在于服务器端。把数据库连接地址、密钥等配置放服务器端前端只取公开配置安全性会好很多。第三养成写测试的习惯。Next.js 对组件测试和 API 测试的支持都比较完善vitest或者jest都行。我做项目时至少会把核心的数据获取函数和接口逻辑覆盖到这样重构的时候心里有底。说回实战本身。我记得第一次完整跑通一个 Next.js 项目并把它部署上线是在一个周五的晚上当时看到线上页面首屏秒开顺滑得不像自己写的那种成就感还是很踏实的。之后我再回看最初在 Vite 项目里绕的那些路愈发觉得 Next.js 不是在“替代”什么而是把从前需要自己拼装的很多零件直接集成好了。它带来的不仅是代码层面的便捷更是一种“一个框架搞定整条链路”的从容。希望你读完这篇文章后也能动手做一个自己的项目把这些经验真正变成手上的功夫。
返回列表