2026年,Nuxt4 已经成为 Vue 生态中最成熟、最高效的全栈框架。它彻底解决了传统 SSR 开发的所有痛点,将「服务端渲染的 SEO 优势」与「客户端 SPA 的丝滑体验」完美融合,同时提供了开箱即用的工程化能力。本文将从零基础开始,带你全面掌握 Nuxt4 的核心概念、实战技巧与最佳实践,最终构建一个高性能的文章系统(完美适配 ThinkPHP 后端)。
一、初识 Nuxt4:为什么它是现代 Web 开发的首选?
Nuxt4 是基于 Vue 3 和 Nitro 2.0 构建的全栈应用框架,它的核心定位是「让开发者专注于业务逻辑,无需关心复杂的 SSR 配置与工程化问题」。
1.1 Nuxt4 的核心优势
- 零配置 SSR:开箱即用的服务端渲染能力,无需手动搭建 Node 服务器和 webpack 配置
- 岛屿式水合:彻底解决传统全量水合的性能问题和 DOM 不匹配警告
- 全栈开发体验:同一项目中同时编写前端页面和后端 API,无缝衔接
- 极致性能:Nitro 2.0 引擎带来 60%+ 的渲染速度提升和 40% 的内存占用降低
- 自动导入:组件、组合式函数、API 自动导入,无需手动 import
- 多环境部署:一次编写,可部署到 Node.js 服务器、静态托管、Serverless、边缘函数等多种环境
1.2 环境搭建与项目初始化
Nuxt4 要求 Node.js 版本 ≥ 20.x,使用官方脚手架一键创建项目:
# 创建最新版 Nuxt4 项目npx nuxi init@latest my-nuxt-app# 进入项目目录cdmy-nuxt-app# 安装依赖npminstall# 启动开发服务器npmrun dev启动成功后,访问http://localhost:3000即可看到 Nuxt4 的欢迎页面。
1.3 Nuxt4 标准项目结构
Nuxt4 采用了更清晰的app/目录结构,将应用代码集中管理,前后端分离更明确:
my-nuxt-app/ ├── app/ # 应用核心代码 │ ├── components/ # 全局组件(自动导入) │ ├── pages/ # 页面组件(约定式路由) │ ├── layouts/ # 布局组件 │ ├── composables/ # 组合式函数(自动导入) │ └── app.vue # 应用入口组件 ├── public/ # 静态资源(直接映射到根路径) ├── server/ # 服务端代码(API 路由、中间件) │ ├── api/ # API 接口 │ └── middleware/ # 服务端中间件 ├── shared/ # 前后端共享工具函数 ├── nuxt.config.ts # Nuxt 配置文件 └── tsconfig.json # TypeScript 配置二、核心概念:约定大于配置的开发哲学
Nuxt4 遵循「约定大于配置」的原则,通过目录结构自动生成路由、组件导入等功能,极大提升了开发效率。
2.1 约定式路由:无需手动配置路由表
Nuxt4 会根据app/pages/目录下的文件结构自动生成路由,无需手动编写路由配置。
基础路由
app/pages/index.vue→ 对应路由/app/pages/article/list.vue→ 对应路由/article/listapp/pages/about.vue→ 对应路由/about
动态路由
使用方括号[]定义动态路由参数:
app/pages/article/[id].vue→ 对应路由/article/1、/article/2- 在组件中通过
useRoute()获取参数:
<script setup lang="ts"> const route = useRoute() const articleId = route.params.id // 获取动态参数 id </script>嵌套路由
创建与文件同名的目录即可实现嵌套路由:
app/pages/article.vue+app/pages/article/[id].vue→ 嵌套路由结构- 在父组件中使用
<NuxtPage />显示子路由内容
2.2 布局系统:统一页面结构
Nuxt4 的布局系统允许你定义多个全局布局,不同页面可以使用不同的布局。
默认布局
创建app/layouts/default.vue,所有页面默认使用该布局:
<template> <div class="app"> <header>网站头部</header> <main> <NuxtPage /> <!-- 页面内容将在这里渲染 --> </main> <footer>网站底部</footer> </div> </template>自定义布局
创建app/layouts/admin.vue,在页面中通过definePageMeta指定使用该布局:
<!-- app/pages/admin/dashboard.vue --> <script setup lang="ts"> definePageMeta({ layout: 'admin' // 使用 admin 布局 }) </script>2.3 自动导入:告别繁琐的 import
Nuxt4 会自动导入以下目录中的内容,无需手动 import:
app/components/:所有 Vue 组件app/composables/:所有组合式函数server/api/:所有 API 接口(通过$fetch调用)
例如,创建app/components/ArticleCard.vue后,可以直接在任何页面中使用:
<template> <ArticleCard :article="item" /> </template>三、SSR 核心原理:岛屿式水合的革命性突破
Nuxt4 最大的创新是岛屿式水合(Island Hydration),它彻底解决了传统全量水合的性能问题和 DOM 不匹配警告。
3.1 传统全量水合的痛点
传统 SSR 框架采用「全量水合」模式:
- 服务端渲染完整 HTML 并返回给浏览器
- 浏览器下载客户端 JS 包
- 对整个页面的所有 DOM 节点进行比对和水合,绑定事件和响应式
这种模式存在三个致命问题:
- 性能差:水合过程耗时久,尤其是复杂页面
- 易出错:任何微小的 DOM 不一致都会导致水合失败,事件绑定失效
- 资源浪费:纯静态内容也需要水合,增加了 JS 包体积
3.2 Nuxt4 岛屿式水合的核心逻辑
Nuxt4 将页面拆分为静态岛屿和交互岛屿:
- 静态岛屿:纯展示内容(文章标题、正文、图片),永不水合,只在服务端渲染为静态 HTML,不发送任何客户端 JS
- 交互岛屿:需要用户交互的组件(分页按钮、点赞按钮、评论框),仅局部水合,只对这些组件进行事件绑定和响应式处理
这种模式带来了革命性的提升:
- 水合耗时减少 90%+:只水合需要交互的组件
- 彻底消除 DOM 不匹配警告:静态内容不参与水合比对
- JS 包体积大幅减小:纯静态内容不需要发送客户端 JS
- 首屏可交互时间(TTI)显著提升:页面加载后立即可以交互
3.3 岛屿式水合的使用方式
Nuxt4 默认开启岛屿式水合,无需额外配置。你只需要遵循一个简单的原则:
纯展示内容直接写在页面中,交互逻辑封装成独立组件
例如,文章列表页的实现:
<!-- app/pages/article/list.vue --> <script setup lang="ts"> // 服务端获取文章列表数据 const { data: articleList } = await useServerFetch('/api/article/list', { query: { page: 1, limit: 10 } }) </script> <template> <div class="article-list"> <!-- 静态岛屿:文章列表,纯展示,永不水合 --> <div v-for="item in articleList" :key="item.id" class="article-item"> <h2>{{ item.title }}</h2> <p>{{ item.description }}</p> <!-- 交互岛屿:点赞按钮,仅局部水合 --> <LikeButton :article-id="item.id" :like-count="item.likeCount" /> </div> <!-- 交互岛屿:分页组件,仅局部水合 --> <Pagination :total="total" :page="page" @change="getPageData" /> </div> </template>四、数据请求:SSR 友好的全栈数据获取方案
Nuxt4 提供了三个核心数据获取 API,完美适配 SSR 场景,解决了传统数据请求的跨域和环境隔离问题。
4.1 useFetch:通用数据获取(推荐)
useFetch是 Nuxt4 最常用的数据获取 API,它会自动根据执行环境选择请求方式:
- 服务端渲染时:在 Nuxt 服务端发起请求,无跨域问题
- 客户端导航时:在浏览器发起请求,享受 SPA 体验
基础用法
<script setup lang="ts"> // 获取文章列表 const { data, pending, error, refresh } = await useFetch('/api/article/list', { query: { page: 1, limit: 10 }, // 查询参数 method: 'GET', // 请求方法 pick: ['list', 'total'] // 只保留需要的字段 }) // 响应式参数:当 page 变化时自动重新请求 const page = ref(1) const { data: articleList } = await useFetch('/api/article/list', { query: { page, limit: 10 } }) </script> <template> <div v-if="pending">加载中...</div> <div v-else-if="error">加载失败:{{ error.message }}</div> <div v-else> <div v-for="item in articleList.list" :key="item.id"> {{ item.title }} </div> </div> </template>4.2 useServerFetch:仅服务端请求
useServerFetch只在服务端执行,客户端永远不会执行这个请求,适合以下场景:
- 需要 SEO 的首屏核心数据
- 敏感数据请求(如需要携带服务器密钥的接口)
- 避免暴露后端接口地址
文章列表第一页的最佳实践
<script setup lang="ts"> // 仅在服务端执行,请求 ThinkPHP 后端接口 const { data: articleList } = await useServerFetch('http://api.xxx.com/article/list', { query: { page: 1, limit: 10 } }) </script>✅ 优势:
- 无跨域问题(服务端之间的请求不受同源策略限制)
- 后端接口地址不会暴露给客户端
- 首屏数据直接渲染在 HTML 中,SEO 效果最佳
4.3 $fetch:手动发起请求
$fetch是 Nuxt4 内置的轻量级 HTTP 客户端,基于ofetch实现,支持服务端和客户端环境。适合在事件处理函数中发起请求:
<script setup lang="ts"> // 分页请求:客户端执行,直连 ThinkPHP 后端 const getPageData = async (targetPage: number) => { const res = await $fetch('http://api.xxx.com/article/list', { query: { page: targetPage, limit: 10 } }) articleList.value = res.list page.value = targetPage } </script>五、状态管理与鉴权:内置 API 解决所有问题
Nuxt4 内置了useState和useCookie两个核心 API,无需引入第三方状态管理库(如 Pinia),即可实现跨组件状态共享和 SSR 友好的 Token 鉴权。
5.1 useState:跨组件状态共享
useState是 Nuxt4 内置的状态管理 API,支持 SSR,状态会在服务端和客户端之间自动同步。
全局用户状态管理
创建app/composables/useAuth.ts:
// app/composables/useAuth.tsinterfaceUser{id:numberusername:stringavatar:string}exportconstuseAuth=()=>{// 定义全局状态,key 为唯一标识constuser=useState<User|null>('user',()=>null)constisLoggedIn=computed(()=>!!user.value)// 登录方法constlogin=async(username:string,password:string)=>{constres=await$fetch('http://api.xxx.com/user/login',{method:'POST',body:{username,password}})user.value=res.userreturnres}// 退出登录方法constlogout=async()=>{await$fetch('http://api.xxx.com/user/logout',{method:'POST'})user.value=null}return{user,isLoggedIn,login,logout}}在任何组件中直接使用:
<script setup lang="ts"> const { user, isLoggedIn, logout } = useAuth() </script> <template> <div v-if="isLoggedIn"> 欢迎,{{ user.username }} <button @click="logout">退出登录</button> </div> <div v-else> <NuxtLink to="/login">登录</NuxtLink> </div> </template>5.2 useCookie:SSR 友好的 Cookie 操作
useCookie是 Nuxt4 内置的 Cookie 操作 API,支持服务端和客户端环境,完美解决 SSR 场景下的 Token 鉴权问题。
Token 双存储方案(服务端+客户端通用)
// app/composables/useAuth.tsexportconstuseAuth=()=>{constuser=useState<User|null>('user',()=>null)constisLoggedIn=computed(()=>!!user.value)// 使用 useCookie 存储 Token,服务端和客户端都能读取consttoken=useCookie<string>('token',{maxAge:7*24*60*60,// 7 天有效期sameSite:'lax',secure:process.env.NODE_ENV==='production'// 生产环境使用 HTTPS})// 登录方法constlogin=async(username:string,password:string)=>{constres=await$fetch('http://api.xxx.com/user/login',{method:'POST',body:{username,password}})user.value=res.user token.value=res.token// 存储 Token 到 Cookiereturnres}// 退出登录方法constlogout=async()=>{await$fetch('http://api.xxx.com/user/logout',{method:'POST'})user.value=nulltoken.value=null// 清除 Cookie 中的 Token}return{user,isLoggedIn,login,logout,token}}5.3 全局请求拦截器:自动携带 Token
创建app/plugins/axios.ts,配置全局请求拦截器,自动在请求头中携带 Token:
// app/plugins/axios.tsexportdefaultdefineNuxtPlugin(()=>{const{token}=useAuth()// 配置全局 $fetch 拦截器globalThis.$fetch=$fetch.create({onRequest({options}){// 自动携带 Tokenif(token.value){options.headers={...options.headers,Authorization:`Bearer${token.value}`}}},onResponseError({response}){// 处理 Token 过期if(response.status===401){const{logout}=useAuth()logout()navigateTo('/login')}}})})六、实战项目:Nuxt4 + ThinkPHP 文章系统
现在我们将结合 ThinkPHP 后端,实现一个完整的文章系统,包括文章列表、文章详情、登录、点赞、评论等功能。
6.1 文章列表页:首屏服务端渲染 + 客户端分页
<!-- app/pages/article/list.vue --> <script setup lang="ts"> const route = useRoute() const page = ref(Number(route.query.page) || 1) // 第一页:服务端请求,SEO 友好 const { data: initialData } = await useServerFetch('http://api.xxx.com/article/list', { query: { page: page.value, limit: 10 } }) // 本地状态,用于客户端分页更新 const articleList = ref(initialData.list) const total = ref(initialData.total) // 分页请求:客户端执行,无刷新更新 const getPageData = async (targetPage: number) => { const res = await $fetch('http://api.xxx.com/article/list', { query: { page: targetPage, limit: 10 } }) articleList.value = res.list total.value = res.total page.value = targetPage // 更新 URL 参数,支持浏览器前进后退 navigateTo({ query: { page: targetPage } }, { replace: true }) } </script> <template> <div class="article-list"> <h1>文章列表</h1> <!-- 静态内容:文章列表 --> <div v-for="item in articleList" :key="item.id" class="article-item"> <NuxtLink :to="`/article/${item.id}`"> <h2>{{ item.title }}</h2> </NuxtLink> <p>{{ item.description }}</p> <div class="meta"> <span>{{ item.createTime }}</span> <LikeButton :article-id="item.id" :like-count="item.likeCount" /> </div> </div> <!-- 交互组件:分页 --> <ClientOnly> <Pagination :total="total" :page="page" :page-size="10" @change="getPageData" /> </ClientOnly> </div> </template>6.2 文章详情页:服务端渲染完整内容
<!-- app/pages/article/[id].vue --> <script setup lang="ts"> const route = useRoute() const articleId = route.params.id // 服务端请求文章详情,SEO 友好 const { data: article } = await useServerFetch(`http://api.xxx.com/article/${articleId}`) // 客户端请求评论列表 const commentList = ref([]) onMounted(async () => { const res = await $fetch(`http://api.xxx.com/comment/list`, { query: { articleId } }) commentList.value = res.list }) </script> <template> <div class="article-detail"> <h1>{{ article.title }}</h1> <div class="meta"> <span>作者:{{ article.author }}</span> <span>发布时间:{{ article.createTime }}</span> </div> <div class="content" v-html="article.content"></div> <!-- 评论区:客户端渲染 --> <ClientOnly> <CommentSection :article-id="articleId" :comment-list="commentList" /> </ClientOnly> </div> </template>6.3 登录页面:Token 双存储
<!-- app/pages/login.vue --> <script setup lang="ts"> const { login } = useAuth() const router = useRouter() const username = ref('') const password = ref('') const loading = ref(false) const handleLogin = async () => { if (!username.value || !password.value) { alert('请输入账号和密码') return } loading.value = true try { await login(username.value, password.value) router.push('/article/list') } catch (err) { alert('登录失败:' + err.message) } finally { loading.value = false } } </script> <template> <div class="login"> <h1>登录</h1> <input v-model="username" placeholder="账号" /> <input v-model="password" type="password" placeholder="密码" /> <button @click="handleLogin" :disabled="loading"> {{ loading ? '登录中...' : '登录' }} </button> </div> </template>七、水合优化与避坑指南
虽然 Nuxt4 解决了大部分水合问题,但在实际开发中仍有一些需要注意的地方。
7.1 延迟水合策略
Nuxt4 提供了多种延迟水合策略,可以进一步优化性能:
hydrate-on-visible:组件进入视口时再水合hydrate-on-interaction:用户与组件交互时再水合hydrate-never:永远不水合(纯静态组件)
<template> <!-- 评论区进入视口时再水合 --> <CommentSection hydrate-on-visible :article-id="id" /> <!-- 点击按钮时再水合 --> <ShareButton hydrate-on-interaction :article-id="id" /> <!-- 纯静态组件,永远不水合 --> <ArticleContent hydrate-never :content="article.content" /> </template>7.2 常见坑点与解决方案
浏览器 API 只能在客户端使用
- 错误:直接在
<script setup>中使用window、document - 正确:使用
onMounted钩子或process.client判断
<script setup lang="ts"> const screenWidth = ref(0) onMounted(() => { screenWidth.value = window.innerWidth }) </script>- 错误:直接在
v-for 必须使用唯一稳定的 key
- 错误:使用
index作为 key - 正确:使用业务唯一 ID(如
item.id)
<div v-for="item in articleList" :key="item.id"> {{ item.title }} </div>- 错误:使用
使用 ClientOnly 包裹客户端专属组件
- 对于依赖浏览器环境的组件(如富文本编辑器、图表组件),使用
<ClientOnly>包裹
<ClientOnly> <Editor v-model="content" /> </ClientOnly>- 对于依赖浏览器环境的组件(如富文本编辑器、图表组件),使用
八、生产环境部署:多环境部署方案
Nuxt4 支持多种部署方式,你可以根据项目需求选择最合适的方案。
8.1 Node.js 服务器部署(推荐 SSR 项目)
- 本地构建项目:
npmrun build构建完成后,会生成.output目录,这是一个自包含的可执行文件。
- 上传
.output目录到服务器,使用 PM2 启动应用:
# 全局安装 PM2npminstall-gpm2# 启动应用pm2 start .output/server/index.mjs--name"nuxt-app"# 设置开机自启pm2 startup pm2 save- 配置 Nginx 反向代理:
server { listen 80; server_name shturl.cc/VLuoi; location / { proxy_pass http://localhost:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }8.2 静态站点部署(SSG)
如果你的网站内容不经常变化,可以使用静态生成模式:
npmrun generate构建完成后,会生成.output/public目录,里面是纯静态 HTML 文件,可以直接部署到任何静态托管服务(如 Vercel、Netlify、阿里云 OSS)。
8.3 与 ThinkPHP 同服务器部署
将 Nuxt 应用和 ThinkPHP 应用部署在同一台服务器上,通过 Nginx 配置路由区分:
server { listen 80; server_name shturl.cc/VLuoi; # 所有 /api 开头的请求转发到 ThinkPHP location /api { proxy_pass http://localhost:8080; # ThinkPHP 运行地址 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 其他请求转发到 Nuxt location / { proxy_pass http://localhost:3000; # Nuxt 运行地址 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }九、最佳实践与总结
9.1 Nuxt4 开发最佳实践
- 遵循岛屿式水合原则:纯展示内容直接写在页面中,交互逻辑封装成独立组件
- 首屏核心数据用 useServerFetch:保证 SEO 和首屏加载速度
- 交互数据用客户端请求:减轻服务端压力,提升用户体验
- 使用内置 API:优先使用
useState、useCookie,避免引入第三方库 - 合理使用延迟水合:对于非首屏交互组件,使用延迟水合策略
- 开启 TypeScript:Nuxt4 对 TypeScript 有完美支持,能显著提升代码质量和开发效率
9.2 总结
Nuxt4 是目前最成熟、最高效的 Vue SSR 全栈框架,它彻底解决了传统 SSR 开发的所有痛点。通过本文的学习,你已经掌握了 Nuxt4 的核心概念、实战技巧和最佳实践,能够独立开发高性能的 SSR 应用。
Nuxt4 + ThinkPHP 是一套非常优秀的技术组合:Nuxt4 负责前端渲染和用户体验,ThinkPHP 负责后端业务逻辑和数据处理,两者完美互补,兼顾了开发效率、性能和 SEO。无论是个人博客、企业官网还是电商平台,这套技术栈都能轻松应对。
未来,Nuxt 还会继续朝着「全栈开发操作系统」的方向演进,提供更多开箱即用的能力,让开发者能够更加专注于业务逻辑的实现。现在就开始你的 Nuxt4 开发之旅吧!