
我从一个挺普遍的困惑说起很多人用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 idroot和一堆script标签。搜索引擎的爬虫虽然现在能执行JavaScript但执行成本高、收录速度慢而且很多爬虫干脆不执行。你辛苦做出来的内容页在搜索结果里排不上号。做过电商、内容站、企业官网的人对这点应该深有体会。第二个是首屏性能。用户访问一个打包后的React应用浏览器要先下载JS bundle然后解析、执行最后才能把页面画出来。在低端手机和弱网环境下这段“白屏等待”时间可能长达好几秒。用户没有耐心等你跳出率蹭蹭往上涨。第三个是工程复杂度。React本身只是一个视图库路由要装react-router数据请求要装axios或者react-querySSR要自己搭Node服务、处理同构、处理脱水注水……项目稍微大一点配置就铺了一地每个新人都要花大量时间理解工程结构。1.2 Next.js的解题思路预渲染加约定优于配置Next.js解决问题的思路不是“在React外面包一层壳”而是从框架层面重新定义了页面是怎么产生、怎么到达用户的。默认情况下Next.js会在构建时就把你的页面预渲染成静态HTML——这就是SSGStatic Site Generation。搜索引擎拿到的就是完整内容首屏也不用等JS执行完。如果页面数据需要实时更新你可以切换成SSRServer Side Rendering让服务端每次请求时现拼HTML拿到的依然是有内容的完整页面。对于那些“数据变化没那么频繁但也不能完全静态”的场景还有ISRIncremental 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-applatest 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.tsx和app/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里面建议加上。依赖方面核心就两个next和react/react-dom。我踩过的一个坑是React版本和Next.js版本不匹配导致hooks报错、构建失败。现在create-next-app生成的package.json里版本都是锁定兼容的但如果哪天你手动升级了React而忘了升级Next或者反过来很容易出事。升级的时候建议用npm install nextlatest reactlatest react-domlatest一起升。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或者getStaticPaths的params拿到具体的值。多级动态路由支持[id]/[comment]这种写法还支持三个点的catch-all路由[...slug].tsx匹配任意层级的路径很适合做文档站的多级目录。Pages Router时代的页面组件默认是客户端组件所有逻辑在浏览器里跑但页面会先经过服务端渲染如果用了getServerSideProps或者构建期生成如果用了getStaticProps所以它天然支持SEO。这个方案最大的优势是生态成熟——你搜索Next.js相关问题十篇有八篇是基于Pages Router的遇到坑好查资料。3.2 App RouterServer Components带来的范式变化App Router是Next.js 13引入的新架构核心变化有两个一是目录结构从pages变成了app二是引入了Server Components这个新概念。在App Router里除了page.tsx文件对应路由页面还有layout.tsx嵌套布局、loading.tsx路由级加载态、error.tsx路由级错误边界、not-found.tsx404页面这些特殊文件。这意味着很多以前要自己写的内容——加载态、错误处理、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/SWR4.2 Pages Router时代的两个核心函数如果你用的是Pages Router数据获取主要看getStaticProps和getServerSideProps这两个函数它们只能写在同一级的页面文件里并且只在服务端运行。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没有getStaticProps和getServerSideProps这两个函数了思路更直接既然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再不行才上SSRCSR只在组件层面用。拿一个内容站举例。文章详情页数据基本不变SSG优先但网站每天会有新文章发布那就把文章页做成ISRrevalidate设成600秒既保证收录和性能又能10分钟内更新一次。首页的推荐列表我会考虑SSR或者CSR因为它需要根据当前热门内容实时变化而且首页是全站SEO权重最高的页面SSR能让搜索引擎第一次请求就拿到最新内容。后台的统计图表这种只有登录用户能看、不需要SEO的直接在客户端组件里用useEffect加SWR去拉接口CSR就够了成本最低。一句话总结渲染方式优先级是SSG ISR SSR CSR能用低成本方案解决就绝不升级。别一上来就全站SSR服务端压力和SDK成本会把你坑哭。5. 全栈能力API Routes和Server Actions5.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 typeemail nameemail required / textarea namemessage required / button typesubmit提交/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_URLpostgres://xxxx # 服务端专用安全 NEXT_PUBLIC_UMENG_ID123456 # 会被浏览器看到只能放非敏感信息我见过不止一个项目把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里用了fs、child_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 --frombase /app/.next/standalone ./ COPY --frombase /app/.next/static ./.next/static COPY --frombase /app/public ./public EXPOSE 3000 CMD [node, server.js]构建出来的镜像体积会小很多启动也快。部署到服务器之后前面建议放一个Nginx做HTTPS终止和静态资源缓存Java/Rust写的后端服务需要怎么配NginxNext.js就怎么配没什么特殊的。6.3 图片、字体、缓存三板斧Next.js对图片做了很多内置优化前提是你要用next/image组件而不是普通的img标签。Image默认支持懒加载、自动转WebP/AVIF、自动设置响应式尺寸还能帮你限制图片域名在next.config.js里配images.remotePatterns。但这个组件也有个容易踩坑的点它要求你必须设定width和height或者使用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()这类每次执行结果不同的代码服务端渲染和客户端渲染拿到的值不一样二是组件里直接读取了window、document等只在浏览器存在的对象服务端渲染时直接报错或者生成空内容三是第三方库在客户端渲染时动态注入了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: Cant resolve fs这类依赖问题在客户端组件里直接引入Node.js内置模块fs、path、os构建时就会报错。原因很简单浏览器环境根本没有这些模块。很多人写完一个工具函数在服务端能跑结果无意中把它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”这件事建立起完整的认知。这篇文章把骨架给你搭好了剩下的血肉得靠你自己写代码、踩坑、再写代码去填。