尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Next.js与Nuxt.js静态生成深度对比:原理、配置与选型指南

Next.js与Nuxt.js静态生成深度对比:原理、配置与选型指南 静态站点生成这事圈子里讨论好几年了但最近几个月明显又热了起来。一方面是各家云厂商的静态托管越来越便宜另一方面是 Next.js 和 Nuxt.js 把 SSG 的体验做得越来越顺滑以至于不少人开始重新审视是不是可以放弃传统服务器渲染直接用纯静态方案搞定大部分项目这里说的 SSGStatic Site Generation核心思路是在构建阶段就把页面 HTML 生成好而不是等用户请求来了再现场渲染。这样做的直接好处是部署简单、响应快、成本低也不容易被动态渲染拖垮。而 Next.js 和 Nuxt.js 这两个框架分别站在 React 和 Vue 生态里把 SSG 从“上古时代的 Jekyll/Hugo 式模板”推进到了“带数据、带路由、带组件体系”的现代开发模式。这篇文章我不打算写那种“A 框架好还是 B 框架好”的二极管结论而是把两条路线的实现原理、实操配置、典型的坑以及我自己的选型经验都铺开聊一聊。无论你是准备给团队做技术调研还是自己私下想折腾一个博客或文档站应该都能从中找到可以参考的东西。1. 重新认识SSG 到底解决什么问题很多人一提 SSG第一反应就是“博客”。确实博客是 SSG 最传统的应用场景但它能做的远不止这些。我劝你先把“SSG 等于博客”这个刻板印象丢掉否则会错失很多优化思路。1.1 SSG 的独特优势和边界SSG 最本质的特点是内容在构建时固定运行时不再动态生成。打个比方传统的服务端渲染就像是餐厅现点现做你下单了厨房才开始动火而 SSG 就像是提前做好的一批盒饭客人来了直接拿走连加热都省了。这个比喻背后藏着几个实打实的好处响应速度极快。生成出来的 HTML 是纯静态文件CDN 可以直接缓存到节点上用户访问时基本就是“文件读取 网络传输”没有数据库查询、没有模板渲染、没有进程间通信。部署极其简单。你不需要在服务器上装 Node.js 运行时除非用了 ISR 或 SSR 这类动态能力一个 Nginx、一个对象存储、甚至 GitHub Pages 都能跑。成本可以压到很低。静态文件托管几乎是最便宜的托管方式个人项目可以做到零成本商业项目也能省掉大量服务器开销。天然抗流量冲击。因为是静态文件流量再怎么涨CDN 都能扛不存在“并发过高把服务打崩”的场景。但 SSG 的边界也很明显凡是需要“千人千面”的内容它都搞不定。比如用户登录后的个性化页面、实时库存、评论区动态刷新这些都不能靠纯构建时静态化来解决。不是说不能用 SSG而是需要额外接客户端数据请求、或者混合渲染方案来弥补。我在实际项目中判断是否用 SSG就看一条这个页面的核心内容是不是对所有用户都一样如果答案是“绝大部分一样只有少量个人化信息”那就可以用 SSG 客户端增强的混合模式。如果页面完全依赖用户身份来生成那 SSG 就不是第一选择了。1.2 为什么现在值得关注 SSG其实 SSG 不是新东西2015 年左右 Jekyll、Hexo 那一波就很火。但现在的 SSG 和那会儿完全是两个物种变化主要有三点第一组件化开发模式。过去的静态站生成器靠的是模板和 Markdown写点复杂交互非常痛苦。而 Next.js 和 Nuxt.js 本身就是完整的应用框架组件、状态管理、路由、样式方案一应俱全。你是在“写一个应用”只是顺带把它导出成了静态文件。第二混合渲染能力。这两个框架都支持同一个项目里既有 SSG 页面也有按需 SSR 的页面甚至可以细粒度到某个路由的某部分用静态、另一部分用动态。自由度比过去高太多。第三生态和工具链成熟。图片优化、代码分割、预加载、SEO 标签管理这些在过去需要自己拼装的件现在框架都内置了。特别是 Next.js 的 Image 组件和 Nuxt 的 NuxtImg 模块对静态站点的性能优化帮助非常大。所以我的观点是SSG 不是“复古”的工程手段而是现代前端架构里性价比极高的一档选项。关键看你选哪条技术路线以及怎么控制好细节。2. Next.js 的静态生成路线与细节Next.js 从很早开始就把静态生成作为核心卖点之一这也是它当初能在 React 生态里杀出来的重要原因。我对它的使用经历了从 Pages Router 到 App Router 的完整过渡下面重点讲现在的主流做法。2.1 从 Pages Router 到 App Router 的静态化路径Pages Router 时代的静态生成很直白你导出一个getStaticProps框架就会在构建时执行这个函数把返回的数据塞进页面 props 里然后渲染成 HTML。动态路由配合getStaticPaths可以预先列出所有需要生成的路径。这个模式简单好理解直到现在还有大量项目在用。App Router 上线后静态化的思路变了。它不再有getStaticProps这个魔法函数而是改为默认情况下只要不调用动态 API比如cookies()、headers()、searchParams的读取页面就会被视为静态构建时自动生成。配合generateStaticParams函数来声明动态路由需要预渲染的参数列表整体更加内敛、自动化。我自己用 App Router 的实际感受是“默认静态”的设计把心智负担降得很低。你不需要刻意考虑“这个页面要不要静态化”只要别乱用动态 API框架自动帮你物尽其用。这也符合 Next.js 近几年的设计理念——用约定优于配置让正确的事情更容易发生。但这里有个点必须提醒App Router 里的“静态”分两层。第一层是静态预渲染构建时就生成 HTML第二层是静态缓存第一次请求后缓存起来之后复用。后者更像过去的 ISR 或 SWR 的概念不要混淆。如果你想做纯静态导出必须显式打开output: export否则部署环境里可能还是会有 Node 服务在跑。2.2 关键配置与常用模式纯静态导出的第一步是在next.config.ts里加上一句import type { NextConfig } from next; const nextConfig: NextConfig { output: export, // 如果你的静态站部署在一个子路径下比如 GitHub Pages 的 /repo/ // 需要设置 basePath // basePath: /my-repo, images: { unoptimized: true, }, }; export default nextConfig;注意images.unoptimized: true这个配置。Next.js 自带的 Image 组件默认会走它的图片优化服务但这个服务依赖服务器运行时。静态导出模式下没有服务器所以要么关掉优化要么预先用外部工具把图片处理好否则构建会直接报错。动态路由的预渲染核心是generateStaticParams。举个例子你要生成一个博客站路径是/posts/[slug]export async function generateStaticParams() { const posts await getAllPosts(); // 自己实现的抓取逻辑 return posts.map((post) ({ slug: post.slug, })); }构建时框架会调用这个函数依次为每个 slug 生成一个 HTML 页面。这里有个容易被忽略的点generateStaticParams里返回的参数必须是字符串数组或类似的可序列化结构不能返回 Promise也不能依赖动态数据。它更像是一个“构建时的配置清单”而不是数据请求层。App Router 里抓数据的函数比如generateMetadata和页面组件里请求的数据默认会在构建时执行一遍。但有一点要特别留意如果你的页面组件里使用了只在客户端生效的交互组件记得用use client单独标记否则构建时会尝试在服务器环境跑这些组件很容易出问题。我的一个习惯性做法是页面主体组件保持服务器组件Server Component把需要交互的部分弹窗、点赞按钮、筛选器拆成独立的客户端组件。这样静态生成的 HTML 里直接就有完整内容SEO友好交互也不受影响。3. Nuxt.js 的静态生成路线与细节Nuxt.js 这边Vue 生态对 SSG 的支持也一直很在线。尤其是 Nuxt 3 之后基于 Nitro 引擎的预渲染机制比之前版本强了不少。如果你本来就是 Vue 技术栈这条路值得认真看。3.1 生成流程与预渲染机制Nuxt 3 的静态生成本质上是对 Nitro 服务器能力的发挥。你用nuxi build构建时Nitro 会生成一个可以独立运行的服务端程序而当你执行nuxi generate时Nitro 会在构建完成后自动爬取所有路由把每个页面渲染成静态 HTML 并输出到.output/public目录。这里有个关键的判断点Nuxt 的静态生成不是你手动声明“要生成哪些页面”而是框架自己去“发现”页面。你定义好路由结构再写好数据抓取逻辑nuxi generate会自动遍历所有可静态化的路径。对于动态路由比如/posts/[id].vue你需要在generate配置或者useFetch的钩子里提供可能的参数列表。// nuxt.config.ts export default defineNuxtConfig({ nitro: { prerender: { routes: [/, /about], crawlLinks: true, }, }, });crawlLinks: true是默认开启的它会顺着页面里出现的链接继续爬取这既是好事也是坑好处是你不用手动把每条路由都写进配置坏处是一旦有个链接指向了外部的 API 或者不存在的路由构建过程可能卡住或者报错。我的经验是保持crawlLinks开启但把第三方外链都变成纯链接标签不对应实际路由的那些避免被框架误爬。页面数据抓取方面Nuxt 提供了一套非常顺滑的配套script setup langts const { data: post } await useAsyncData(post, () $fetch(/api/posts/${route.params.id}) ); /scriptuseAsyncData配合$fetch在构建时会真实执行一次请求数据会被序列化到页面 payload 里。这意味着如果你把生成站点的服务器地址写在环境变量里构建机的网络一定要能访问到它。很多人在自己的电脑上构建没问题一到 CI 就报错基本都是这个原因。3.2 构建时数据抓取与 payload 优化Nuxt 静态生成后每个页面都会携带一个内联的 payload数据快照用于客户端激活时恢复组件状态。这个机制让页面在静态 HTML 和客户端交互之间保持数据一致用户体验很好但也带来了一个隐患payload 太大页面首屏脚本体积会明显膨胀。所以我一直建议关注.output/public/_payload目录里生成的 JSON 文件大小。如果你的页面请求了一个巨大的列表或者后端接口返回了一堆用不到的字段这些数据都会被原样塞进 payload。可以做的优化有两个方向一是在服务端做数据裁剪。请求时只取前端真正需要的字段比如用$fetch的query参数或后端接口的分页能力。二是按需延迟加载。有些数据不需要在首屏出现可以放到客户端组件里用useFetch按需请求而不是一开始就await。这样静态生成的 HTML 里不带这部分数据 payload体积就小多了。我在一个文档项目里实测过某页面原来的 payload 有 2MB裁剪字段 延迟加载后降到 200KB构建体积和页面加载速度都有明显改善。这算是 Nuxt SSG 项目里性价比最高的优化手段之一。4. 两者横向对比关键差异决定选型聊完各自的技术路线直接把两个框架放到同一张台面上比一比。这里说的对比不是说谁比谁强而是在 SSG 这个特定场景下它们各自的适用位置不同。4.1 核心差异对比表下面这个表格是我根据自己的使用经验整理的基本涵盖了选型时最关心的维度对比维度Next.jsNuxt.js基础生态React/React Server ComponentsVue/Vue Composition API典型静态配置output: exportnuxi generate动态路由预渲染generateStaticParamsprerender.routes 自动爬取数据获取getStaticPropsPages/ RSC 构建时解析useAsyncData/useFetch静态托管适配需注意 Image 组件和 basePath默认输出相对友好学习曲线中高尤其 App Router 概念多中低新手友好适合的项目中大型团队、复杂组件生态中小型项目、Vue 团队、快速上线从表格能看出Next.js 的静态化路径更“工程化”需要开发者对框架的运行机制有更完整的理解Nuxt 则更像一个“开箱即用”的解决方案交给它就能生成一份可用的静态站点不需要想太多底层机制。4.2 场景适用性与生态考量选型这件事脱离团队背景谈优劣都是耍流氓。如果团队已经熟练使用 React或者项目里大量依赖 React 生态的组件库、状态管理方案那 Next.js 是再自然不过的选择。React 生态的积累太厚了几乎任何需求都能找到成熟的现成方案。特别是需要复杂交互、富客户端逻辑的项目React 的组件生态能给到很强的支撑。反过来如果团队是 Vue 背景或者项目本身不算复杂Nuxt 的体验会非常舒畅。Vue 的单文件组件模式在中小型内容站、博客、文档站这些场景下“写得快、改得快”配合 Nuxt 的自动导入和模块系统开发效率确实高。还有一个经常被忽略的点团队的长期维护意愿。Next.js 的迭代节奏比较快App Router 刚出来那会儿很多习惯了 Pages Router 的开发者都被打了个措手不及。Nuxt 3 相对而言更稳妥从 Nuxt 2 升级到 Nuxt 3 虽然也有迁移成本但大方向上 Vue 的核心思想没变过。如果你不希望团队频繁追新Nuxt 会更省心。不过我必须说一句这种对比很容易变成“信仰之争”。我的建议是如果你的项目两种框架都能做那就做一个十几页的原型页面跑通一遍静态生成流程看看哪个团队上手更快、迭代更顺手。纸面上的特性永远不如实际跑一遍来得真实。5. 实操踩坑记录最常见的几个坑不管是 Next.js 还是 Nuxt静态生成在实操中都会遇到一批“看起来怪怪的、报错还不直观”的问题。我把这些年踩过的坑整理成排查清单希望能帮你省点时间。5.1 Next.js 静态导出环节的几个坑第一个坑也是最容易直接卡死构建的Image 组件没配置。前面提过next/image默认走优化服务静态导出必须加images.unoptimized: true。但即便加了如果你用了fill或者src传的是外部 URL构建时依然可能提示无法优化。我的建议是静态导出的项目直接用普通的img标签或者换成next/legacy/image别追这个功能。第二个坑是generateStaticParams返回值不合法。App Router 里这个函数必须返回一个数组数组元素是普通对象不能带undefined值。如果你从数据库里读取的字段可能为空记得先做默认值处理否则构建时会报序列化错误。第三个坑比较隐蔽output: export模式下动态 API 全不可用。凡是用了cookies()、headers()、searchParams的页面会被强制标记为动态渲染和静态导出冲突。构建时不一定会立刻报错但生成的页面可能内容为空或者直接跳过某些路由。遇到这种情况要么把页面改成纯客户端数据请求要么放弃纯静态导出、改用 Node 部署。5.2 Nuxt 生成环节的几个坑Nuxt 这边我遇到最多的坑是构建时外链依赖。项目里的一个按钮链到了某个外部网站结果crawlLinks把那个外链也当成路由去爬了构建过程卡了几分钟才超时。解决办法是在链接上加target_blank和relnoopener或者干脆把外链渲染成纯a标签不让爬虫追踪。另一个常见的坑是useAsyncData的 key 冲突。Nuxt 里每个数据请求都需要一个唯一的 key如果两个不同页面的请求用了同一个 key生成时可能会出现数据串台的情况。给每个请求加上符合语义的前缀比如post-${id}能有效避免这类问题。还有一个容易被忽略的静态站点的NODE_ENV。nuxi generate默认会按开发模式处理某些逻辑如果你在代码里判断了process.env.NODE_ENV可能会导致构建时的行为和线上不一致。建议在 CI 环境里显式设置NODE_ENVproduction让生成结果更接近真实发布状态。6. 选型决策指南你该用哪个到这里两条路线的内部机制和实操细节都过得差不多了。如果你还在犹豫我提供一个我经常用来帮团队做决策的判断清单供你参考。6.1 决策清单站在项目角度你可以依次回答下面三个问题团队主要技术栈是什么这是第一道也是最重要的筛子。React 团队选 Next.jsVue 团队选 Nuxt.js强行跨生态不是不行但学习成本和长期维护成本都会变高。项目内容有多大比例是公共内容如果公共内容占 80% 以上SSG 是合适的两个框架都能满足如果动态内容占比很高你需要重新评估是否真的适合静态方案。团队对框架迭代的接受度怎样愿意持续跟进生态变化、接受新概念的团队Next.js 的长期潜力更大求稳、希望一次写好长期不动的团队Nuxt 更省心。6.2 基于团队与项目背景的参考建议我给你一个相对务实的组合建议如果你是个人博主、独立开发者想做内容站或文档站又是 Vue 背景Nuxt.js 是首选它上手快、生成稳折腾空间小能让你把精力放在内容本身。如果你在电商公司或者团队已经有完整的 React 基础设施要做官网、营销页、产品文档这类对性能有要求、又有一定交互深度的站点Next.js 更合适它的组件生态和优化能力能帮你把站点做到极致。如果项目既是内容站又需要局部动态能力比如用户评论区、搜索页我的经验是框架选哪个都行但架构上一定要把静态和动态的边界划分清楚。哪些路由是构建时生成哪些路由走客户端渲染这个决定了后期维护的舒适度。最后再分享一个我个人的经验做了这么多 SSG 项目之后我发现最后的瓶颈通常不在框架而在内容组织方式。静态站点的上限取决于你把内容模型设计得够不够清晰。框架只是把内容变成页面的流水线流水线再好原料不行也白搭。所以动手写代码之前先把内容结构、分类体系、数据接口这些最基础的事情梳理好再挑框架不迟。
返回列表