news 2026/9/12 10:39:45

Next.js全栈开发实战:从路由到部署的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Next.js全栈开发实战:从路由到部署的完整指南

我从一个挺普遍的困惑说起:很多人用React写了不少项目,组件、状态、Hooks都熟了,但一提到“上线”就开始头疼——首屏白屏、SEO约等于零、路由要自己配、接口还得单独起一个后端服务。这些问题单独拆开都能解决,但合在一起,项目复杂度瞬间就上去了。Next.js就是冲着这一整串问题来的:它把前端页面、路由、服务端渲染、接口能力全部收进同一个框架里,让你用一套代码同时拿到React的开发体验和传统多页应用的性能表现。这篇文章我不会从头讲JSX或者useState,那些是React的范畴,我重点讲的是Next.js区别于普通React项目的核心机制:文件路由、渲染策略、数据获取、全栈接口、部署优化,以及新手最容易踩的坑。

1. 先搞清楚Next.js到底解决了什么问题

1.1 纯React项目的三个老大难

用Create React App或者Vite搭一个React应用,开发期很爽,热更新快、组件化清晰,但到了生产环节,问题一个接一个冒出来。

第一个是SEO。纯前端渲染的页面,浏览器拿到的HTML基本就是一个空壳子,里面只有一个<div id="root">和一堆script标签。搜索引擎的爬虫虽然现在能执行JavaScript,但执行成本高、收录速度慢,而且很多爬虫干脆不执行。你辛苦做出来的内容页,在搜索结果里排不上号。做过电商、内容站、企业官网的人,对这点应该深有体会。

第二个是首屏性能。用户访问一个打包后的React应用,浏览器要先下载JS bundle,然后解析、执行,最后才能把页面画出来。在低端手机和弱网环境下,这段“白屏等待”时间可能长达好几秒。用户没有耐心等你,跳出率蹭蹭往上涨。

第三个是工程复杂度。React本身只是一个视图库,路由要装react-router,数据请求要装axios或者react-query,SSR要自己搭Node服务、处理同构、处理脱水注水……项目稍微大一点,配置就铺了一地,每个新人都要花大量时间理解工程结构。

1.2 Next.js的解题思路:预渲染加约定优于配置

Next.js解决问题的思路不是“在React外面包一层壳”,而是从框架层面重新定义了页面是怎么产生、怎么到达用户的。

默认情况下,Next.js会在构建时就把你的页面预渲染成静态HTML——这就是SSG(Static Site Generation)。搜索引擎拿到的就是完整内容,首屏也不用等JS执行完。如果页面数据需要实时更新,你可以切换成SSR(Server Side Rendering),让服务端每次请求时现拼HTML,拿到的依然是有内容的完整页面。对于那些“数据变化没那么频繁但也不能完全静态”的场景,还有ISR(Incremental Static Regeneration),定时按需重新生成部分页面,兼顾性能和新鲜度。

路由这块,Next.js用的是“文件即路由”的约定:你在pages目录或者app目录下放一个about.tsx文件,就自动有了/about这个页面,完全不用手写路由配置。目录层级嵌套多少层,URL路径就有多深,清晰直观,团队协作时几乎不需要额外沟通。

更关键的是,Next.js是一个全栈框架。你可以在同一个项目里写API接口(API Routes),也可以在Server Component里直接读数据库、调第三方服务,不需要单独维护一个后端工程。前端页面、接口、部署配置全部在一个仓库里,开发链路短了,部署也简单了。

1.3 什么样的人适合直接上手Next.js

我的判断是,如果你属于下面这几类人,可以直接投入:

  • 做内容站、博客、官网、营销页的,SEO和首屏速度是刚需;
  • 做中后台管理系统,但不想维护两套前后端工程,想在一个项目里把页面和接口都解决掉;
  • 接外包、做独立产品开发,时间紧、人手少,需要快速上线,Next.js的全栈能力极其省事;
  • 已经在用React,想平滑过渡到服务端渲染体系的开发者,React知识可以90%直接迁移。

2. create-next-app初始化:认真对待交互式选项

2.1 一条命令背后的架构选择

创建一个Next.js项目很简单,官方提供了脚手架:

npx create-next-app@latest my-app

执行之后,命令行会弹出一连串交互式问题。很多新手嫌麻烦,一路回车用默认值,后面写代码时就发现哪儿哪儿不对劲。这些选项不是摆设,它们直接改变了项目的基础架构,我建议你至少把这几个选项过一遍脑子。

  • TypeScript选不选:不用犹豫,选Yes。Next.js对TypeScript的支持在框架级项目里属于第一梯队,.tsx文件在编辑器里的类型提示、重构安全、接口数据形状约束,能帮你拦下一大批低级错误。如果你还不会TypeScript,趁这个机会一起学,性价比极高。
  • ESLint选不选:选Yes。它配合Next.js内置的eslint-config-next,能在开发阶段直接标出哪些写法会导致hydration问题、哪些图片用法不规范,相当于一个经验丰富的同事在帮你做code review。
  • Tailwind CSS选不选:取决于项目风格。如果你喜欢原子化CSS,而且目标是快速做UI,选上能省很多事。但如果你要接入的是公司已有的组件库或者设计系统,可以选No,后面自己接。
  • App Router还是Pages Router:这是最关键的选项。App Router是Next.js 13之后主推的新架构,支持Server Components、嵌套布局、流式渲染,是未来方向;Pages Router是经典方案,社区资料多、踩坑分享多,上手逻辑更直观。我的建议是,新项目无脑App Router,除非你维护的是老项目。后面我会专门讲两者的取舍。

2.2 初始目录拆解:知道每个文件夹在干嘛

初始化完成后,项目的核心结构大概是这样的:

my-app/ ├── app/ # App Router目录,页面和布局都在这里 │ ├── layout.tsx # 全局布局,整个应用共享的壳 │ ├── page.tsx # 首页,对应路径 / │ └── globals.css # 全局样式 ├── public/ # 静态资源,图片、favicon、下载文件都丢这里 ├── src/ # 如果你选用了src目录,源码都放这里 ├── next.config.js # Next.js配置文件,重定向、图片域名、webpack配置都在这 ├── tsconfig.json # TypeScript配置 └── package.json # 项目依赖和脚本

动手写代码之前,建议先搞明白app/layout.tsxapp/page.tsx的关系。layout是整个应用的外壳——导航栏、页脚、侧边栏这些所有页面共用的部分放在layout里,layout会一直存在,切换页面时不会重新渲染。page是具体某个路由的内容。一个路由页面会渲染在layout的{children}的位置上。你可以嵌套layout,比如在app/blog/下再放一个layout.tsx,它就只对/blog下的页面生效,非常适合做“博客区统一风格、首页和关于页各自独立”这类需求。

2.3 package.json里的关键脚本和依赖

Next.js项目默认给好了几个脚本,含义要清楚:

{ "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint" } }
  • npm run dev:起开发服务,默认端口3000,带热更新,写代码即时生效。
  • npm run build:构建生产版本,这一步会做页面预渲染、生成静态文件、做自动优化。
  • npm start:启动生产服务,必须在build完成之后运行,默认端口也是3000。
  • npm run lint:代码检查,CI里面建议加上。

依赖方面,核心就两个:nextreact/react-dom。我踩过的一个坑是React版本和Next.js版本不匹配,导致hooks报错、构建失败。现在create-next-app生成的package.json里版本都是锁定兼容的,但如果哪天你手动升级了React而忘了升级Next,或者反过来,很容易出事。升级的时候,建议用npm install next@latest react@latest react-dom@latest一起升。

2.4 项目初始化后先做三件事

第一,改layout.tsx里的metadata。Next.js从metadata对象读取页面的标题、描述、Open Graph信息,这些直接影响SEO。默认模板里的标题是“Create Next App”,不改成自己网站的标题和描述,上线之后搜索引擎抓到的全是默认内容,那跟没做SEO没区别。

export const metadata = { title: '我的产品官网', description: '这是给用户看的一句话描述,会出现在搜索结果里', };

第二,把.gitignore确认一遍,确保.next(构建产物)和node_modules没有被提交。很多新手把项目传到GitHub上给同事看,结果同事clone下来跑不起来,多半就是node_modules传进去或者.next垃圾文件干扰了。

第三,跑一遍生产构建。别只在dev模式下看着页面正常就完事了——dev模式不会暴露很多构建期问题。执行npm run build,看看有没有warning和error,特别关注哪些页面被静态化了、哪些是动态渲染的,做到心里有数。

3. 路由系统实战:Pages Router和App Router怎么选

3.1 Pages Router:经典但依然能打

Pages Router是Next.js从早期版本就有的路由体系,写法非常直白:在pages目录下创建文件,文件名就是URL路径。

pages/ ├── index.tsx # 对应 / ├── about.tsx # 对应 /about └── blog/ ├── index.tsx # 对应 /blog └── [slug].tsx # 对应 /blog/xxx,动态路由

[slug].tsx里的方括号就是动态参数,在组件里通过useRouter().query.slug或者getStaticPathsparams拿到具体的值。多级动态路由支持[id]/[comment]这种写法,还支持三个点的catch-all路由[...slug].tsx,匹配任意层级的路径,很适合做文档站的多级目录。

Pages Router时代的页面组件默认是客户端组件,所有逻辑在浏览器里跑,但页面会先经过服务端渲染(如果用了getServerSideProps)或者构建期生成(如果用了getStaticProps),所以它天然支持SEO。这个方案最大的优势是生态成熟——你搜索Next.js相关问题,十篇有八篇是基于Pages Router的,遇到坑好查资料。

3.2 App Router:Server Components带来的范式变化

App Router是Next.js 13引入的新架构,核心变化有两个:一是目录结构从pages变成了app,二是引入了Server Components这个新概念。

在App Router里,除了page.tsx文件对应路由页面,还有layout.tsx(嵌套布局)、loading.tsx(路由级加载态)、error.tsx(路由级错误边界)、not-found.tsx(404页面)这些特殊文件。这意味着很多以前要自己写的内容——加载态、错误处理、404——框架已经帮你做了约定,你只需要把对应文件放到位。

Server Components是一个非常关键的变化。简单说,App Router里的组件默认在服务端执行,它可以直接读数据库、调API、拿结果,而浏览器端根本不会下载这些组件的JavaScript。这样页面的JS体积大幅减小,首屏更快。当你需要交互逻辑时,在组件顶部加一行'use client',它就变成了客户端组件,可以正常使用useState、useEffect这些Hooks。

// app/dashboard/page.tsx // 这是Server Component,直接查数据库拿数据,浏览器不下载本组件的JS import { getPosts } from '@/lib/db'; export default async function DashboardPage() { const posts = await getPosts(); return ( <ul> {posts.map((p) => ( <li key={p.id}>{p.title}</li> ))} </ul> ); }

3.3 动态路由在App Router里的写法

App Router的动态路由文件约定和Pages Router大同小异,用方括号表示动态段:

app/ ├── blog/ │ ├── layout.tsx │ ├── page.tsx # 对应 /blog │ └── [slug]/ │ └── page.tsx # 对应 /blog/xxx

动态参数通过组件的params属性拿到:

// app/blog/[slug]/page.tsx interface PageProps { params: { slug: string }; } export default async function BlogPostPage({ params }: PageProps) { const post = await getPost(params.slug); return <article>{post.content}</article>; }

在App Router中,generateStaticParams替代了Pages Router的getStaticPaths,用来在构建期生成动态路由的静态页面列表。配合dynamicParams字段的默认行为,还能控制那些不在generateStaticParams里的路径是走404还是动态渲染兜底。这个设计很灵活,但初学者容易忽略:如果你的页面是动态内容,不加任何配置的话,App Router默认是会动态渲染的,跟Pages Router的静态行为有差别。

3.4 我的实际选型建议

在Next.js 15之前,App Router还有一个比较要命的问题——API稳定性。刚出的时候,Server Components的某些API在迭代中发生过破坏性变更,网上教程鱼龙混杂,跟着老教程写新版本项目,代码可能直接跑不起来。不过这个问题在14、15之后已经好很多了,核心API基本稳定了。

我做新项目的选择是:一律App Router。理由很实际——Vercel官方明确后续重心全在App Router上,新特性只在App Router里加,Pages Router只做维护不再加功能。现在入坑如果用Pages Router,过一两年做大版本升级时还是要迁到App Router,不如现在就适应。但如果你维护的是已经上线的Pages Router老项目,没必要急着重构,跑得好好的就别动,只有当你要大改版时再顺势迁移。

4. 数据获取:SSR、SSG、ISR、CSR的区别与取舍

4.1 四种渲染方式一张表看懂

Next.js最核心的能力就是这几种渲染方式,理解它们,才算真正理解了Next.js。

渲染方式生成时机数据新鲜度适合场景Next.js中如何实现
SSG(静态生成)构建时构建时更新,之后不变博客文章、文档、营销页getStaticProps/ 默认静态生成
SSR(服务端渲染)每次请求时每次请求最新个性化页面、实时数据getServerSideProps/ 动态渲染
ISR(增量静态生成)构建时+定时/按需更新按分钟级更新有变化但不频繁的内容revalidate字段
CSR(客户端渲染)浏览器端随用户操作更新后台图表、高度交互组件useEffect/SWR

4.2 Pages Router时代的两个核心函数

如果你用的是Pages Router,数据获取主要看getStaticPropsgetServerSideProps这两个函数,它们只能写在同一级的页面文件里,并且只在服务端运行。

getStaticProps在构建时执行一次,把拿到的数据作为props传给页面组件。配合revalidate可以实现ISR:

// pages/blog/[slug].tsx export async function getStaticProps({ params }: { params: { slug: string } }) { const post = await getPost(params.slug); return { props: { post }, revalidate: 60, // 每60秒重新验证一次,如果数据变化则后台重新生成 }; }

这里的60秒不是“每60秒更新一次”而是“每60秒最多重新生成一次”,而且触发条件是有用户访问。如果有10个用户在第59秒访问,可能只有1个人等到了新数据,其余9人拿的是旧缓存,然后后台触发重新生成。理解这一点很重要,否则你会觉得ISR的更新时机“不符合预期”。

getServerSideProps则是每次请求都实时执行,拿到最新数据,适合需要严格实时性的场景。但代价是每个请求都要经过服务端计算,访问量上来后对服务端压力非常大。不建议为了省事把每个页面都用getServerSideProps

4.3 App Router:在Server Component里直接获取数据

App Router没有getStaticPropsgetServerSideProps这两个函数了,思路更直接:既然Server Component本来就在服务端执行,那就直接写异步代码拿数据,不需要额外封装。

// app/blog/[slug]/page.tsx export default async function BlogPostPage({ params }: { params: { slug: string } }) { // 直接await,服务端处理 const post = await getPost(params.slug); return <article>{post.content}</article>; }

默认情况下,这个页面是静态生成还是动态渲染,取决于页面里有没有使用动态API(比如cookies()headers()),以及fetch请求有没有设置缓存配置。这里的“自动判断”对新手来说可能有点黑魔法,我建议这么做:

  • 如果页面内容不依赖用户、不需要实时更新,默认静态生成即可,性能最好;
  • 如果你希望这个路由每次请求都动态渲染,什么内容都实时取,在页面顶部加export const dynamic = 'force-dynamic',或者对fetch请求传{ cache: 'no-store' }
  • 如果你要ISR效果,对fetch传{ next: { revalidate: 60 } }
// 强制ISR效果:缓存1分钟 const res = await fetch('https://api.example.com/posts', { next: { revalidate: 60 }, });

4.4 实际项目里我的选择逻辑

我自己的经验是:能用静态就静态,不行再想ISR,再不行才上SSR,CSR只在组件层面用。

拿一个内容站举例。文章详情页数据基本不变,SSG优先;但网站每天会有新文章发布,那就把文章页做成ISR,revalidate设成600秒,既保证收录和性能,又能10分钟内更新一次。首页的推荐列表,我会考虑SSR或者CSR,因为它需要根据当前热门内容实时变化,而且首页是全站SEO权重最高的页面,SSR能让搜索引擎第一次请求就拿到最新内容。后台的统计图表这种只有登录用户能看、不需要SEO的,直接在客户端组件里用useEffect加SWR去拉接口,CSR就够了,成本最低。

一句话总结:渲染方式优先级是SSG > ISR > SSR > CSR,能用低成本方案解决就绝不升级。别一上来就全站SSR,服务端压力和SDK成本会把你坑哭。

5. 全栈能力:API Routes和Server Actions

5.1 API Routes:在Next.js里写后端接口

Next.js允许你在同一个项目里实现后端接口。Pages Router时代是pages/api/目录,App Router时代是app/api/目录,写一个route.ts文件:

// app/api/posts/route.ts import { NextResponse } from 'next/server'; export async function GET() { const posts = await getPosts(); return NextResponse.json(posts); } export async function POST(request: Request) { const body = await request.json(); const newPost = await createPost(body); return NextResponse.json(newPost, { status: 201 }); }

文件所在的目录层级决定接口路径,app/api/posts/route.ts对应/api/posts。方法名称对应HTTP动词,想支持GET就导出GET,支持POST就导出POST。这套写法的好处是,你不需要去nginx里配路由转发、不需要跨域配置(API和页面同源)、不需要担心前端请求地址联调不一致。

要注意的是,API Routes运行在Node.js运行时里,所以代码里可以安全地访问环境变量、读文件系统、连接数据库。但这也带来一个隐患:如果把API Routes部署到Serverless环境(比如Vercel),每个接口函数都是独立的执行环境,数据库连接不能像传统后端那样常驻,得用连接池或者按请求创建连接,否则并发一高就出问题。

5.2 Server Actions:表单提交的新姿势

App Router里还有一个比API Routes更“激进”的做法——Server Actions。你可以在Server Component里定义一个async函数,然后直接在表单的action属性里调用它,这个函数在服务端执行,客户端根本接触不到它的代码。

// app/contact/page.tsx 'use server'; async function submitContactForm(formData: FormData) { const email = formData.get('email'); const message = formData.get('message'); // 验证、写库、发邮件…… await saveToDatabase({ email, message }); } export default function ContactPage() { return ( <form action={submitContactForm}> <input type="email" name="email" required /> <textarea name="message" required /> <button type="submit">提交</button> </form> ); }

用Server Actions做表单提交,省掉了一整套“客户端状态 + fetch + 处理loading + 处理错误”的代码,体验顺畅得很。不过它的适用场景主要就是表单提交和简单的数据变更,如果是需要给第三方App提供完整的REST API、要做权限控制矩阵、要批量操作,还是老老实实用API Routes。

服务端执行的函数,有一点必须时刻记住:你不能隐式信任任何来自客户端的数据。表单提交到了服务端,要重新校验、重新过滤,你在前端做的所有校验都只是用户体验,服务端校验才是安全边界。

5.3 环境变量和密钥安全

全栈开发中最容易犯的错误之一,就是把密钥暴露到浏览器端。Next.js的环境变量有两套前缀:不带前缀的MY_SECRET,只能在服务端读取,不会暴露给客户端;带NEXT_PUBLIC_前缀的NEXT_PUBLIC_API_URL,会被打进客户端bundle里,任何人打开浏览器开发者工具都能看到。

# .env.local DATABASE_URL=postgres://xxxx # 服务端专用,安全 NEXT_PUBLIC_UMENG_ID=123456 # 会被浏览器看到,只能放非敏感信息

我见过不止一个项目把Stripe密钥、数据库密码直接用NEXT_PUBLIC_前缀写进去,等于把后门焊在了首页源码里。一定要有一个刻进DNA的认知:凡是NEXT_PUBLIC_前缀的变量,你必须假设全世界都能看到。

另外,.env.local要确保在.gitignore里,避免提交到Git仓库。团队协作时,提供一个.env.example文件,把需要的变量名都列出来,不填真实值,让同事自己复制一份填自己的本地配置。

6. 部署与性能优化:开发环境跑通只是开始

6.1 部署到Vercel:最省心的路线

Next.js和Vercel是同一家公司,部署体验是全球顶级的水准。你只需要把代码推到GitHub,然后在Vercel后台导入仓库,它会自动识别Next.js、自动装依赖、自动build,最后给你一个xxx.vercel.app的域名。之后每次git push到主分支,它还会自动触发重新构建和部署,真正的CI/CD一体化。

Vercel部署还有个好处是自动处理了CDN缓存、Serverless函数分配、Preview预览环境(每个PR都会自动生成一个独立的预览链接,方便你review)。如果你的项目是个人项目、创业产品、或者对部署成本敏感的中小规模应用,直接用Vercel,别自己折腾服务器。

需要注意的一点是,Vercel默认的Serverless环境对Node.js内置模块支持有限。如果在API Routes里用了fschild_process这种依赖完整Node运行时的功能,部署到Vercel上可能直接报错,你需要在next.config.js里把对应的函数配置成Node.js运行时。

6.2 自建服务器:自己和Docker硬扛

如果项目有合规要求、或者必须部署在国内服务器、或者要对接内网数据库,Vercel就不合适了,得自己搭环境。

Next.js自带一个独立部署模式,可以在构建时输出一份精简的自包含文件:

// next.config.js module.exports = { output: 'standalone', };

执行npm run build之后,.next/standalone目录里就是一套独立的可运行文件,包含服务端代码和最小化的node_modules,拷贝到服务器上运行node server.js就行。配合Docker部署,一个像样的Dockerfile可以精简成这样:

FROM node:20-alpine AS base WORKDIR /app COPY . . RUN npm ci FROM node:20-alpine WORKDIR /app COPY --from=base /app/.next/standalone ./ COPY --from=base /app/.next/static ./.next/static COPY --from=base /app/public ./public EXPOSE 3000 CMD ["node", "server.js"]

构建出来的镜像体积会小很多,启动也快。部署到服务器之后,前面建议放一个Nginx做HTTPS终止和静态资源缓存,Java/Rust写的后端服务需要怎么配Nginx,Next.js就怎么配,没什么特殊的。

6.3 图片、字体、缓存三板斧

Next.js对图片做了很多内置优化,前提是你要用next/image组件而不是普通的<img>标签。<Image>默认支持懒加载、自动转WebP/AVIF、自动设置响应式尺寸,还能帮你限制图片域名(在next.config.js里配images.remotePatterns)。但这个组件也有个容易踩坑的点:它要求你必须设定widthheight,或者使用fill属性配合父容器,否则会警告甚至报错。刚开始用可能觉得麻烦,用久了就知道这个约束是为了防止布局偏移(CLS)。

字体这块,next/font是性能神器。它会在构建时自动内联字体文件,避免浏览器额外发起字体请求,还能自动处理字体子集化,只加载当前页面用到的字形。对于中文网站来说效果尤其明显,中文字体文件动辄好几MB,不优化的话首屏会多出一大截下载量。

缓存策略上,除了前面说的ISRrevalidate,还有一层是CDN缓存。如果你用Vercel,它会在全球节点自动缓存静态资源。如果是自建Nginx,可以在静态资源响应头里加上一年期的Cache-Control,因为Next.js构建出的静态文件文件名都带hash,内容变了文件名就变了,浏览器和CDN缓存旧版本无妨。

7. 常见报错和排查思路:踩过的坑一次性交代

7.1 Hydration Error:客户端和服务端渲染不一致

这是新手最容易碰到的报错,完整描述是Hydration failed because the initial UI does not match what was rendered on the server。含义是:服务端渲染出来的HTML,和客户端JS首次渲染产生的HTML不一致,React不知道该怎么把两者衔接起来,直接罢工了。

最常见的触发原因有三个:一是组件里用了Date.now()Math.random()这类每次执行结果不同的代码,服务端渲染和客户端渲染拿到的值不一样;二是组件里直接读取了windowdocument等只在浏览器存在的对象,服务端渲染时直接报错或者生成空内容;三是第三方库在客户端渲染时动态注入了DOM样式或class。

排查路径很明确,先看控制台完整报错信息,它会指向具体是哪个组件、哪个属性对不上。找到之后,解决办法通常是把这些不稳定的内容放到useEffect里渲染,或者用dynamic函数配合ssr: false导入这个组件,跳过服务端渲染:

import dynamic from 'next/dynamic'; const Chart = dynamic(() => import('@/components/Chart'), { ssr: false });

7.2 修改了代码但页面没反应

开发环境下改了代码页面不刷新,或者改了接口参数但请求没变化,大概率是Next.js的缓存机制在“作怪”。App Router开启了对fetch请求的默认缓存,同一个URL的请求在构建期内可能复用缓存结果,导致你改了数据源但页面还显示旧数据。

处理办法:如果你确实需要每次请求都拿最新数据,在fetch请求里显式关掉缓存:

const res = await fetch('https://api.example.com/data', { cache: 'no-store', });

另外,.next目录是构建缓存目录,有时候改了配置不生效,可以执行rm -rf .next && npm run dev试试。别怕删,它就是个本地缓存,每次运行都会重新生成。

7.3Module not found: Can't resolve 'fs'这类依赖问题

在客户端组件里直接引入Node.js内置模块(fspathos),构建时就会报错。原因很简单:浏览器环境根本没有这些模块。很多人写完一个工具函数在服务端能跑,结果无意中把它import进了客户端组件,立刻就报错。

排查思路:看报错信息里提示的文件路径,找到那个在客户端组件里引入Node模块的文件。解决方案是把调用Node模块的逻辑放到API Routes或Server Component里,或者用dynamic加载,保证它只在服务端执行。这也是我在前面强调“App Router默认服务端组件是个优势”的原因——服务端组件天然可以用Node模块,不容易出这个问题。

7.4 生产环境首屏慢:先检查是不是没有做静态生成

开发环境一切飞快,build完部署上线,首屏却慢得离谱。我遇到过的案例,十有八九是页面被动态渲染了。打开浏览器DevTools看Network面板,如果HTML响应时间是几百毫秒甚至几秒,说明这个页面在服务端实时计算,而没有走静态生成或ISR。

检查方法很直接:在npm run build的输出日志里,看一下每个路由的符号标记。表示静态生成,ƒ表示动态渲染,表示ISR。如果你发现本该静态化的页面标了ƒ,这就是首屏慢的原因。解决思路就是前面讲过的,优先用静态生成,确实需要实时数据的部分再单独做SSR或者CSR。

我个人在实际项目里的习惯是,每次build完都会花一分钟扫一遍构建输出,看看哪些页面成了动态渲染,再对照业务需求判断是合理还是失误。这个习惯帮我提前发现了不少性能问题。

8. 最后的经验之谈

如果你正要开始学Next.js,我的建议是别贪多。先把App Router的文件约定吃透,把layout、page、loading、error这几张“约定牌”用好,再把数据获取的四种方式逐一带进项目里跑一遍,切身感受一下它们的差别。等你有两三个页面能顺利上线了,再去碰Server Actions、中间件、流式渲染这些进阶能力。

还有一个小技巧,遇到问题时优先看官方文档,起码先确认你搜到的内容跟自己的Next.js版本对得上。Next.js前后几个大版本的API变化挺大的,你看到一篇2022年的文章讲Pages Router的用法,拿来套2025年的App Router项目,大概率是不匹配的。学会用一个简单方法判断:打开项目的node_modules/next/package.json,看一眼实际的版本号,再选择对应版本的文档。

Next.js上手不难,但想“精通”,本质上靠的是你对“一个页面是怎么从代码变成用户看到的HTML”这件事建立起完整的认知。这篇文章把骨架给你搭好了,剩下的血肉,得靠你自己写代码、踩坑、再写代码去填。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 10:38:44

SpringBoot+Vue+MySQL校园资产管理平台:从源码到部署答辩全攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 10:38:33

多征兆域特征提取在工业设备状态监测中的应用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 10:37:23

微信小程序社区团购系统开发实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 10:37:12

STM32C5A3R ADC电压采集全流程:从CubeMX配置到DMA滤波校准

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 10:35:22

Linux自学指南:从入门到实战的12天学习路径

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 10:33:04

二叉树算法实战:从递归到迭代的C++实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华