标签

Nuxt.js

Nuxt.js 是一个基于 Vue.js 的高级框架,用于构建服务器端渲染(SSR)的应用程序、静态站点生成(SSG)或单页应用程序(SPA)。它为开发者提供了一个强大的架构,以简化 Web 开发流程,特别是在处理视图层的渲染时。Nuxt.js 的目的是让 Web 开发变得简单而强大,同时也提供了自动化的代码分割和性能优化。

Nuxt.js
服务端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` 与页面通信。