服务端5月29日 01:55
Nuxt.js 的路由系统和 Vue Router 有什么不同?核心区别:Vue Router 需要手动在 router.ts 中声明路由表(path/component 映射);Nuxt 基于文件系统自动生成路由——pages/ 目录的文件路径即路由路径。例如 pages/user/[id].vue 自动映射为 /user/:id,pages/user/index.vue 映射为 /user。Nuxt 还内置了 layouts/ 布局系统、middleware/ 路由中间件(替代 Vue Router 的 beforeEnter/全局守卫),以及 NuxtPage 和 NuxtLayout 组件处理嵌套路由渲染。底层仍使用 Vue Router,但零配置。
## 追问
- Nuxt 的嵌套路由怎么定义?和 Vue Router 的 children 有什么对应关系?
- 路由中间件的执行顺序是什么?全局、布局、页面级中间件谁先执行?
- 动态路由 [id].vue 在 SSG 模式下需要额外配置吗?
- 如何在 Nuxt 中自定义 Vue Router 的 scrollBehavior?
- Nuxt 3 的 typedPages 实验特性是做什么的?
## 写段代码
```vue
<!-- pages/user/[id].vue -->
<script setup>
definePageMeta({
middleware: 'auth',
layout: 'dashboard'
})
const route = useRoute()
const id = route.params.id
</script>
```标签
Nuxt.js
Nuxt.js 是一个基于 Vue.js 的高级框架,用于构建服务器端渲染(SSR)的应用程序、静态站点生成(SSG)或单页应用程序(SPA)。它为开发者提供了一个强大的架构,以简化 Web 开发流程,特别是在处理视图层的渲染时。Nuxt.js 的目的是让 Web 开发变得简单而强大,同时也提供了自动化的代码分割和性能优化。

服务端5月29日 01:55
Nuxt.js 中如何管理状态?useState 和 Pinia 怎么选?Nuxt 3 推荐两种方案:内置的 `useState()` composable 和 Pinia(通过 `@pinia/nuxt` 模块集成)。`useState()` 适合跨组件共享简单响应式状态,天然 SSR 安全——服务端和客户端状态自动同步,无需额外处理水合。Pinia 适合复杂状态管理,支持 actions、getters、插件生态,且同样对 SSR 友好。Vuex 是 Nuxt 2 时代的默认方案,Nuxt 3 已不再内置。
## 追问
- `useState()` 和 `ref()` 在 SSR 场景下有什么区别?为什么不能用 `ref()` 共享状态?
- Pinia store 在 Nuxt 3 中如何避免跨请求状态污染?
- `useState()` 的底层实现原理是什么?它是怎么保证 SSR 水合一致的?
- 什么场景下 `useState()` 不够用,必须上 Pinia?
- Nuxt 3 的 `useState()` 可以在普通 JS 文件中使用吗?还是必须在 setup 中?
## 写段代码
```js
// composables/useCounter.js - useState 方案
export const useCounter = () => useState('counter', () => 0)
// stores/user.js - Pinia 方案
export const useUserStore = defineStore('user', () => {
const user = ref(null)
const login = async (creds) => { user.value = await $fetch('/api/login', creds) }
return { user, login }
})
```服务端5月29日 01:55
Nuxt.js 的 SSG 和 SSR 有什么区别?怎么选?SSG 在构建时(nuxt generate)遍历所有路由,执行数据获取逻辑,生成完整静态 HTML 文件,部署到任意静态服务器即可,请求零服务端开销;SSR 在每次请求时由 Node 服务动态渲染 HTML,内容实时但需要持续运行服务器。Nuxt 3 通过 routeRules 支持混合模式:同一个应用中,博客页 SSG、商品页 ISR(swr 缓存)、后台 SPA,按路由粒度选择渲染策略,不再需要二选一。
## 追问
- ISR(Incremental Static Regeneration)在 Nuxt 3 里怎么实现?和 Next.js 的 ISR 有什么区别?
- SSG 构建时动态路由太多怎么办?如何按需生成?
- 混合模式下 SSR 和 SSG 页面的 payload 传递机制有区别吗?
- SSG 页面的客户端数据怎么保持更新?
- Nitro 的预设(preset)对 SSG/SSR 部署有什么影响?
## 写段代码
```js
// nuxt.config.ts - 混合渲染策略
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true }, // SSG
'/blog/**': { swr: 3600 }, // ISR 1小时
'/dashboard/**': { ssr: false }, // SPA
'/api/**': { cors: true } // API路由
},
nitro: { preset: 'cloudflare-pages' }
})
```服务端5月29日 01:54
Nuxt.js 的模块和插件有什么区别?模块(Module)在 Nuxt 初始化阶段执行,能修改构建配置、注册插件和中间件,是 Nuxt 核心扩展机制,通过 `nuxt.config` 的 `modules` 数组注册。插件(Plugin)在 Vue 实例化前后执行,用于注册全局组件、指令或注入实例属性,放在 `plugins/` 目录自动注册。关键区别:模块可以注册插件,反过来不行;模块可发布为 npm 包(如 `@nuxtjs/axios`),插件通常项目内使用。
## 追问
- 模块的执行时机为什么比插件早?如果插件需要修改 Nuxt 配置怎么办?
- `modules` 数组中的顺序会影响什么?多个模块冲突如何处理?
- Nuxt 3 中模块和插件的注册方式有什么变化?
- 如何编写一个可发布的 Nuxt 模块?需要导出什么?
- 插件的 `mode` 属性(client/server)在 Nuxt 3 中被什么替代了?
## 写段代码
```js
// nuxt.config.js - 注册模块和插件
export default {
modules: ['@pinia/nuxt', '@nuxtjs/i18n'],
plugins: ['~/plugins/vue-gtag.client.js']
}
// plugins/vue-gtag.client.js
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(VueGtag, { config: { id: 'G-XXX' } })
})
```服务端5月29日 01:54
Nuxt.js 应用性能优化有哪些关键手段?Nuxt 性能优化分三层:构建层利用 Nitro 做 payload 提取,将 SSR 数据抽到独立 JSON 避免重复内联;渲染层通过 routeRules 按Route规则混合 SSR/SSG/SPA,静态页面用 prerender 避免运行时渲染开销;资源层用 @nuxt/image 自动生成响应式 srcset 和 WebP 格式,LazyHydrate 延迟水合非首屏组件。此外 Nuxt 3 基于 Vite 的按需编译和 tree-shaking 本身就比 Nuxt 2 的 Webpack 产物更小。
## 追问
- payload extraction 具体做了什么?对 TTFB 有多大影响?
- @nuxt/image 的 ipx 服务是做什么的?和直接用 CDN 图片处理有什么区别?
- Nuxt 3 的 LazyHydrate 组件怎么用?和 Vue 的 Suspense 有什么关系?
- Nitro 的缓存策略(cachedFunctions)怎么配置?
- 静态资源加 hash 缓存后,怎么处理版本更新问题?
## 写段代码
```js
// nuxt.config.ts - 性能优化配置
export default defineNuxtConfig({
modules: ['@nuxt/image'],
routeRules: {
'/blog/**': { swr: 3600 }, // ISR: 1小时缓存
'/admin/**': { ssr: false }, // SPA: 跳过SSR
'/': { prerender: true } // SSG: 构建时生成
},
experimental: { payloadExtraction: true }
})
```服务端5月29日 01:54
Nuxt 3 有哪些数据获取方式?useFetch 和 useAsyncData 怎么选?Nuxt 3 提供两个核心组合式函数:useFetch 和 useAsyncData。useFetch 是 useAsyncData + $fetch 的语法糖,99% 场景用 useFetch 即可;useAsyncData 适合需要自定义获取逻辑(如依赖多个 API 聚合数据)的场景。两者都在 SSR 时于服务端执行,结果序列化到 payload 中供客户端 hydration 复用,避免重复请求。底层 HTTP 客户端是 ofetch($fetch),支持服务端直接调用,无跨域问题。
## 追问
- useFetch 的 key 是干什么用的?不写会怎样?
- SSR 时数据怎么从服务端传到客户端?hydration mismatch 怎么避免?
- $fetch 和 useFetch 的区别是什么?能不能在组件里直接用 $fetch?
- server/api 里的接口怎么和 useFetch 配合?
- 如何给 useFetch 加请求缓存和去重?
## 写段代码
```js
// 自定义 key + transform
const { data, pending } = await useFetch(
`/api/user/${route.params.id}`,
{
key: `user-${route.params.id}`,
transform: (res) => res.data,
getCachedData(key, nuxt) {
return nuxt.payload.data[key]
}
}
)
```服务端5月29日 01:54
Nuxt.js 和普通 Vue 应用有什么区别?Nuxt.js 是基于 Vue 的全栈框架,核心区别在于:普通 Vue 是纯客户端渲染 SPA,需要手动配置 Vue Router、Pinia、Vite 等生态;Nuxt 内置了 SSR/SSG/SPA 三种渲染模式、基于 pages/ 目录的文件路由、组件和组合式函数的自动导入、server/api 目录的 BaaS 风格服务端路由,以及 Nitro 服务引擎。普通 Vue 应用首屏为空壳 HTML,SEO 差;Nuxt 默认 SSR 可输出完整 HTML,对 SEO 和首屏性能更友好。
## 追问
- Nuxt 3 的自动导入是怎么实现的?和手动 import 有什么性能差异?
- 什么场景下应该选 SPA 模式而非 SSR?
- Nuxt 的 server/api 目录和传统后端 API 有什么区别?
- Nuxt 3 为什么用 Nitro 替代了 Nuxt 2 的服务端架构?
- 如果项目从 Vue CLI 迁移到 Nuxt,最大的改造成本在哪?
## 写段代码
```js
// nuxt.config.ts - 三种渲染模式切换
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true }, // SSG
'/api/**': { cors: true }, // 服务端路由
'/dashboard': { ssr: false } // SPA
}
})
```服务端5月28日 02:27
Nuxt.js 应用如何部署和托管?有哪些推荐的部署方案?Nuxt.js 部署方式取决于渲染模式:SSR 需要 Node.js 运行时,SSG/SPA 只需静态托管。推荐 Vercel(零配置 SSR + 自动预览)和 Docker + Nginx + PM2(自建服务器可控),中小项目上 Vercel,企业级走容器化。
## 三种渲染模式对应不同部署策略
Nuxt.js 支持 SSR、SSG、SPA 三种渲染模式,部署架构差异显著:
**SSR(服务端渲染)**:`nuxt build` 产出 Node.js 服务,必须运行在服务器上。启动命令 `node .output/server/index.mjs`,需配合进程管理器(PM2)和反向代理(Nginx)。
**SSG(静态站点生成)**:`nuxt generate` 产出纯静态文件到 `.output/public`,部署到任何静态托管平台即可,无需服务器运行时。适合博客、文档站、企业官网。
**SPA(单页应用)**:`nuxt.config.ts` 中设置 `ssr: false`,构建后部署方式同 SSG。适合不需要 SEO 的后台管理系统。
Nuxt 3+ 基于 Nitro 服务引擎,构建产物自包含(内联依赖、无 node_modules),且支持 `routeRules` 实现混合渲染——同一应用中不同路由可分别使用 SSR 和 SSG。
## Vercel 部署:零配置 SSR 首选
Vercel 是 Nuxt 官方推荐平台,原生支持 SSR/SSG/混合渲染:
1. 项目推送到 GitHub/GitLab
2. Vercel 导入仓库,框架自动识别 Nuxt
3. 环境变量在 Dashboard → Settings → Environment Variables 配置
4. 每次 push 自动构建部署,PR 自动生成预览 URL
SSR 模式下 Vercel 将服务自动部署到 Serverless Functions 或 Edge Runtime。免费额度(100GB 带宽/月)适合中小项目,高流量场景注意计费。Nuxt 4 已适配 Vercel 的 `NITRO_PRESET=vercel-edge`,可启用边缘渲染。
## 自建服务器:Docker + Nginx + PM2
生产环境自建推荐 Docker 容器化,多阶段构建控制镜像体积:
```dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/.output .output
ENV HOST=0.0.0.0 PORT=3000
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]
```
Nginx 反向代理 + 静态资源缓存配置:
```nginx
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location /_nuxt/ {
proxy_pass http://127.0.0.1:3000;
expires 365d;
add_header Cache-Control "public, immutable";
}
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
```
PM2 集群模式:`pm2 start .output/server/index.mjs --name nuxt-app -i max`,自动利用多核 CPU 并提供进程守护。
## 静态托管:Cloudflare Pages / Netlify / GitHub Pages
SSG 产物可零成本部署到静态托管平台:
- **Cloudflare Pages**:免费额度高(500次构建/月、无限带宽),全球 CDN,`npx wrangler pages deploy .output/public` 一行命令部署
- **Netlify**:自动部署 + 表单处理 + Functions 扩展,适合需要后端能力的静态站
- **GitHub Pages**:完全免费,配合 GitHub Actions 自动构建,适合文档站和博客
## 面试追问
**Q: SSR 和 SSG 如何选择?**
内容更新频繁(电商、社交动态)选 SSR;内容稳定(博客、文档)选 SSG。也可用 `routeRules` 混合渲染:`routeRules: { '/': { prerender: true }, '/api/**': { ssr: true } }`,首页预渲染 + 接口页实时渲染。
**Q: Nuxt 3 的 Nitro 和 Nuxt 2 的部署有什么区别?**
Nuxt 2 构建产物依赖 `node_modules`,需完整上传;Nuxt 3 Nitro 产物自包含,体积小 70%+。Nitro 支持 20+ 部署预设(Vercel、Cloudflare Workers、AWS Lambda、Deno Deploy),通过 `NITRO_PRESET` 环境变量一键切换,无需改代码。
**Q: 如何管理环境变量避免泄露?**
`nuxt.config.ts` 中用 `runtimeConfig` 区分公开和私密变量:`public.xxx` 暴露给客户端,根级变量仅服务端可用。部署时在平台面板注入,不写 `.env` 提交到仓库。Vercel/Docker 均支持运行时注入。服务端5月28日 02:26
Nuxt.js 中如何处理错误和进行调试?Nuxt.js 的错误处理和调试涉及多个层次:页面级、组件级、服务端 API 级,以及全局兜底。不同版本的 Nuxt(2 vs 3)API 差异较大,下面分别说明核心机制。
## 页面级错误处理
Nuxt 通过错误页面捕获路由渲染阶段的异常,给用户友好的降级体验。
**Nuxt 2** 在 `layouts/error.vue` 中定义错误页,组件接收 `error` prop(含 `statusCode` 和 `message`):
\`\`\`vue
<!-- layouts/error.vue -->
<template>
<div>
<h1 v-if="error.statusCode === 404">页面不存在</h1>
<h1 v-else>服务器错误</h1>
<p>{{ error.message }}</p>
<nuxt-link to="/">返回首页</nuxt-link>
</div>
</template>
<script>
export default {
props: ['error'],
layout: 'blank'
}
</script>
\`\`\`
**Nuxt 3** 错误页改为项目根目录的 `~/error.vue`(与 `app.vue` 同级),写法基于 Composition API:
\`\`\`vue
<!-- error.vue -->
<script setup>
const props = defineProps({ error: Object })
const handleError = () => clearError({ redirect: '/' })
</script>
<template>
<div>
<h1>{{ error.statusCode }}</h1>
<p>{{ error.message }}</p>
<button @click="handleError">返回首页</button>
</div>
</template>
\`\`\`
关键区别:Nuxt 3 使用 `clearError()` 清除错误状态,而不是自动恢复。
## 数据获取中的错误捕获
异步数据获取是错误高发区,Nuxt 2 和 3 的处理方式不同。
**Nuxt 2** 在 `asyncData` 中用 `error()` 函数跳转到错误页:
\`\`\`javascript
export default {
async asyncData({ params, $axios, error }) {
try {
const user = await $axios.$get(\`/api/users/\${params.id}\`)
return { user }
} catch (err) {
error({ statusCode: 404, message: '用户不存在' })
}
}
}
\`\`\`
**Nuxt 3** 使用 `useFetch` / `useAsyncData`,通过 `createError()` 抛出结构化错误:
\`\`\`javascript
const { data, error } = await useFetch(\`/api/users/\${route.params.id}\`)
if (error.value) {
throw createError({ statusCode: 404, message: '用户不存在' })
}
\`\`\`
`useFetch` 返回的 `error` 是响应式引用,可以直接在模板中展示,不必跳转错误页。
## 组件级错误边界
Nuxt 3 提供了 `<NuxtErrorBoundary>` 组件,隔离组件内的客户端错误,避免整个页面崩溃:
\`\`\`vue
<NuxtErrorBoundary>
<UserWidget />
<template #error="{ error }">
<p>组件加载失败:{{ error.message }}</p>
<button @click="error = null">重试</button>
</template>
</NuxtErrorBoundary>
\`\`\`
适合用在仪表盘、信息流等局部模块,一个模块出错不影响其他区域。
## 服务端 API 错误处理
Nuxt 3 的 server routes 需要正确抛出 HTTP 错误:
\`\`\`javascript
// server/api/users/[id].js
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
const user = await db.user.findById(id)
if (!user) {
throw createError({
statusCode: 404,
statusMessage: 'User not found'
})
}
return user
})
\`\`\`
服务端的 `createError()` 会自动设置 HTTP 状态码和响应体,客户端通过 `useFetch` 的 `error` 接收。注意不要在错误消息中拼接用户输入,避免注入风险。
## 全局错误钩子
Nuxt 3 提供了生命周期钩子统一捕获错误:
\`\`\`javascript
// plugins/error-handler.js
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('vue:error', (error, instance, info) => {
console.error('Vue error:', error, info)
// 上报到 Sentry
})
nuxtApp.hook('app:error', (error) => {
console.error('App error:', error)
})
})
\`\`\`
- `vue:error`:Vue 组件渲染或生命周期中未捕获的错误
- `app:error`:Nuxt 初始化、插件加载等阶段的错误
配合 Sentry 或 LogRocket 可以实现错误监控和报警。
## 中间件中的错误处理
路由中间件里不能直接 `throw`,要用 `abortNavigation()` 中断导航:
\`\`\`javascript
// middleware/auth.js
export default defineNuxtRouteMiddleware((to, from) => {
const token = useCookie('token')
if (!token.value) {
return abortNavigation(
createError({ statusCode: 401, message: '请先登录' })
)
}
})
\`\`\`
Nuxt 2 的中间件则用 `redirect()` 或 `error()` 处理,逻辑类似但 API 不同。
## 调试方法
**开发环境调试:**
- `nuxt dev` 启动开发服务器,热重载 + source maps,直接在浏览器 DevTools 断点
- Nuxt DevTools(Nuxt 3 专有):集成组件树、路由信息、payload 检查、auto-imports 可视化,通过 `nuxi dev --enable-devtools` 开启
- Vue DevTools:查看组件 props、响应式数据、事件流
**服务端调试:**
\`\`\`javascript
// nuxt.config.ts
export default defineNuxtConfig({
devServer: {
debug: true
}
})
\`\`\`
配合 VS Code 的 Node.js 调试配置,可以给 server routes 加断点:
\`\`\`json
{
"type": "node",
"request": "launch",
"name": "Nuxt Server",
"program": "\${workspaceFolder}/node_modules/.bin/nuxi",
"args": ["dev"],
"console": "integratedTerminal"
}
\`\`\`
**构建产物分析:**
\`\`\`bash
npx nuxi analyze
\`\`\`
分析打包体积,定位过大的依赖,优化加载性能。
**SSR 水合不匹配排查:**
服务端渲染的 HTML 与客户端 hydration 不一致时,控制台会报 `Hydration mismatch` 警告。常见原因:
- 使用了 `Date.now()` 等运行时不确定的值
- 条件渲染依赖了 `window` 等仅客户端的对象
- 解决方案:用 `<ClientOnly>` 包裹客户端专属内容,或用 `onMounted` 延迟赋值
## 常见问题
**chunk 加载失败**:新部署后旧页面的 JS chunk URL 失效,Nuxt 3 会自动硬重载;Nuxt 2 需手动监听 `window.onerror` 中的 chunk 错误并刷新。
**500 错误定位**:优先检查 server routes 的 try-catch 是否遗漏,用 `console.error` 打印完整堆栈,确认 API 调用参数和返回格式。
**asyncData 报错但页面空白**:Nuxt 2 中 `asyncData` 未调用 `error()` 时,错误会被静默吞掉。确保 catch 块中调用 `error()` 或至少 `console.error`。
服务端5月28日 02:02
Nuxt.js 的布局系统是如何工作的?如何创建和使用自定义布局?## 布局系统的核心机制
Nuxt.js 的布局系统本质上是一个页面级别的"外壳组件"。每个页面都会被包裹在某个布局中,布局负责渲染导航栏、侧边栏、页脚等公共结构,页面内容通过插槽注入。
在 Nuxt 3 中,布局文件放在 `layouts/` 目录下,使用 `<slot />` 渲染页面内容(Nuxt 2 使用 `<Nuxt />`,已废弃)。页面通过 `definePageMeta` 指定布局名称,框架自动完成匹配。整个流程是:`app.vue` 中的 `<NuxtLayout>` 读取当前页面的 `layout` 元信息,加载对应的布局组件,再通过 `<slot />` 把页面内容插入布局的指定位置。
## 默认布局的工作方式
`layouts/default.vue` 是所有页面的默认外壳。只要这个文件存在,未被显式指定其他布局的页面都会自动使用它。
```vue
<!-- layouts/default.vue -->
<template>
<div>
<nav>
<NuxtLink to="/">首页</NuxtLink>
<NuxtLink to="/dashboard">控制台</NuxtLink>
</nav>
<main>
<slot />
</main>
<footer>© 2026 MyApp</footer>
</div>
</template>
```
`app.vue` 中需要用 `<NuxtLayout>` 包裹 `<NuxtPage />`,布局系统才会生效:
```vue
<!-- app.vue -->
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
```
如果 `app.vue` 中直接写 `<NuxtPage />` 而没有 `<NuxtLayout>` 包裹,即使页面声明了 `layout: 'auth'`,布局也不会渲染——这是初学者最容易踩的坑。
## 创建自定义布局
在 `layouts/` 下新建 `.vue` 文件即创建了一个布局。文件名(去掉扩展名)就是布局名称,支持嵌套目录,名称会自动转为 kebab-case。例如 `layouts/admin-panel.vue` 对应布局名 `admin-panel`。
以一个认证页面布局为例:
```vue
<!-- layouts/auth.vue -->
<template>
<div class="auth-wrapper">
<div class="auth-card">
<slot />
</div>
</div>
</template>
<style scoped>
.auth-wrapper {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
}
.auth-card {
background: #fff;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0,0,0,0.1);
width: 400px;
}
</style>
```
## 在页面中指定布局
Nuxt 3 使用 `definePageMeta` 编译器宏来声明布局,它在编译时处理,没有运行时开销:
```vue
<!-- pages/login.vue -->
<script setup>
definePageMeta({
layout: 'auth'
})
</script>
<template>
<form @submit.prevent="handleLogin">
<input v-model="email" type="email" placeholder="邮箱" />
<input v-model="password" type="password" placeholder="密码" />
<button type="submit">登录</button>
</form>
</template>
```
Nuxt 2 的写法是 `export default { layout: 'auth' }`,Nuxt 3 已不推荐。`definePageMeta` 的好处是它不参与响应式系统,不会出现在最终的客户端 bundle 中。
## 动态切换布局
某些场景下需要根据运行时条件切换布局,比如管理员和普通用户看到不同外壳。有两种方式:
**方式一:在页面模板中使用 `NuxtLayout` 动态 `name`**
```vue
<!-- pages/dashboard.vue -->
<script setup>
const route = useRoute()
const layoutName = computed(() =>
route.path.startsWith('/admin') ? 'admin' : 'default'
)
</script>
<template>
<NuxtLayout :name="layoutName">
<div>控制台内容</div>
</NuxtLayout>
</template>
```
**方式二:在路由中间件中修改 `to.meta.layout`**
```javascript
// middleware/layout.js
export default defineNuxtRouteMiddleware((to) => {
if (to.path.startsWith('/admin')) {
to.meta.layout = 'admin'
}
})
```
方式一适合页面内部的条件判断,方式二适合全局性的布局策略(比如在 `nuxt.config.ts` 中配置全局中间件)。注意:方式一在页面中又嵌了一层 `NuxtLayout`,实际上会产生双重布局包裹,需要同时在 `definePageMeta` 中设置 `layout: false` 来禁用自动布局。
## 具名插槽传递区域内容
布局支持 Vue 的具名插槽,页面可以向布局的特定区域注入内容。这在需要灵活控制头部或侧边栏时特别有用:
```vue
<!-- layouts/custom.vue -->
<template>
<div>
<header>
<slot name="header">
<h1>默认标题</h1>
</slot>
</header>
<div class="content-wrapper">
<aside>
<slot name="sidebar" />
</aside>
<main>
<slot />
</main>
</div>
</div>
</template>
```
页面中使用 `template #slotName` 传入内容:
```vue
<!-- pages/profile.vue -->
<script setup>
definePageMeta({ layout: 'custom' })
</script>
<template>
<template #header>
<h1>用户中心</h1>
</template>
<template #sidebar>
<ul>
<li>基本信息</li>
<li>安全设置</li>
</ul>
</template>
<div>个人资料编辑区域</div>
</template>
```
具名插槽的 `<slot name="header">` 中可以写默认内容,页面不传 `#header` 时自动展示默认值,传了则覆盖——这比在布局里用 `v-if` 判断灵活得多。
## 禁用布局与自定义页面过渡
某些页面(如全屏展示页、打印页)不需要任何布局外壳,用 `layout: false` 关闭:
```vue
<script setup>
definePageMeta({
layout: false
})
</script>
<template>
<div class="fullscreen">全屏内容,不受任何布局包裹</div>
</template>
```
布局切换时还可以配置过渡动画。在 `nuxt.config.ts` 中设置 `layoutTransition`:
```javascript
// nuxt.config.ts
export default defineNuxtConfig({
app: {
layoutTransition: { name: 'layout', mode: 'out-in' }
}
})
```
```css
/* assets/css/main.css */
.layout-enter-active,
.layout-leave-active {
transition: opacity 0.3s ease;
}
.layout-enter-from,
.layout-leave-to {
opacity: 0;
}
```
## 布局与页面的生命周期顺序
了解执行顺序对调试很重要。Nuxt 3 中布局和页面的生命周期执行顺序为:
1. 布局的 `setup()` 执行
2. 布局的 `onMounted` 注册(此时 DOM 未挂载)
3. 页面的 `setup()` 执行
4. 页面的 `onMounted` 注册
5. 页面的 `onMounted` 回调触发
6. 布局的 `onMounted` 回调触发
布局的 `onMounted` 反而在页面之后触发,因为布局要等插槽内容(即页面)渲染完毕才算挂载完成。如果需要在布局中访问插槽内容的 DOM,要等 `onMounted` 而非 `setup` 阶段。
## 常见坑与排查
**布局不生效?** 检查 `app.vue` 是否用 `<NuxtLayout>` 包裹了 `<NuxtPage />`。没有这一层,布局系统不会启动,页面声明的 `layout` 会被忽略。
**页面内容空白?** 布局文件中必须包含 `<slot />`(Nuxt 3)或 `<Nuxt />`(Nuxt 2),否则页面内容无处渲染。
**布局名称不匹配?** `layouts/custom-layout.vue` 的布局名是 `custom-layout`,不是 `customLayout`。`definePageMeta` 中的 `layout` 值要与 kebab-case 名称一致。
**Nuxt 2 项目迁移?** 三件事:把布局中的 `<Nuxt />` 替换为 `<slot />`;把页面中的 `layout` 选项改为 `definePageMeta({ layout: 'xxx' })`;把错误页面从 `layouts/error.vue` 移到项目根目录的 `error.vue`。
**布局中获取页面数据?** 布局无法直接调用 `useAsyncData` 或 `useFetch` 获取数据(它不是路由组件),但可以通过 `useRoute` 获取路由参数,或通过 `provide/inject` 与页面通信。