Nuxt.js(Vue 生态的全栈框架)在 Vercel 上有着极佳的支持:通过内置的 Nitro Server 引擎,Nuxt 项目可以完美适配 Vercel Functions 的运行时,实现 SSR(服务端渲染)、SSG(静态生成)、API Routes、Server Middleware 等所有能力。本文从初始化项目到生产部署,逐个讲解 Nuxt 在 Vercel 上的最佳实践。
一、项目初始化与模式选择
1.1 创建 Nuxt 项目
# 创建项目
npx nuxi@latest init my-nuxt-app
# 进入目录并安装依赖
cd my-nuxt-app
npm install
# 安装 Vercel 适配器(必须)
npm install -D @nuxtjs/vercel-builder
# 实际 Nuxt Nitro 自带 vercel 预设,通常不需要额外安装
Nuxt 3 的 Nitro 引擎自带多种 preset,其中 vercel 是专门为 Vercel Functions 优化过的。
1.2 SSR vs SSG vs ISR:怎么选?
Nuxt 的渲染模式由 nuxt.config.ts 中的 ssr 和 routeRules 决定:
| 模式 | 配置 | 适用场景 | Vercel 适配 |
|---|---|---|---|
| SSR | ssr: true(默认) | 需要服务端数据的动态页、登录态页面 | ✅ Nitro → Vercel Serverless Functions |
| SSG | ssr: true + routeRules: { '/**': { isr: false, prerender: true } } | 静态站、博客、文档 | ✅ 静态文件直接上传到 Vercel CDN |
| ISR | routeRules: { '/**': { isr: 60 } } | 大部分内容不常变但需及时更新 | ✅ 混合:静态 CDN + Serverless stale-while-revalidate |
| SPA | ssr: false | 纯后台管理、强客户端交互应用 | ✅ 仅 HTML + JS 文件 |
建议决策:
- 内容站/博客:SSG(最快、最省 Serverless 调用)
- SaaS 前台:ISR(兼顾速度和数据新鲜度)
- 后台管理系统:SSR 或 SPA
1.3 nuxt.config.ts 核心配置
// nuxt.config.ts
export default defineNuxtConfig({
// 默认开启 SSR
ssr: true,
// 静态资源/缓存策略
routeRules: {
// 首页 SSR(动态内容)
'/': { isr: 60 },
// 博客列表页:ISR 5 分钟刷新一次
'/blog/**': { isr: 300 },
// 博客详情页:静态生成 + 按需重新验证
'/blog/**': { prerender: true, isr: 3600 },
// 关于页:纯静态,永不重新验证
'/about': { prerender: true },
// API 路由:不走缓存
'/api/**': { cors: true, isr: false },
},
nitro: {
// 显式指定 Vercel 预设
preset: 'vercel',
// 为 Vercel Functions 设置内存和超时
vercel: {
functions: {
maxDuration: 10, // 函数最大执行时间(秒)
},
},
},
// 开发服务器代理(本地开发用)
devtools: { enabled: true },
compatibilityDate: '2025-01-01',
});
*preset: 'vercel'* 是核心配置,它告诉 Nitro 将服务端代码打包为 Vercel Functions 兼容的格式。
1.4 Vue 3 Composition API 与 Vercel 最佳实践
Nuxt 3 默认使用 Vue 3 的 <script setup> 语法,但在 Vercel SSR 环境下,服务端与客户端的执行边界需要特别留意。
onMounted vs useAsyncData
| 特性 | onMounted | useAsyncData |
|---|---|---|
| 执行时机 | 仅在客户端挂载后 | 服务端 + 客户端均可 |
| SEO 友好 | 否(初始 HTML 无数据) | 是(SSR 时直接渲染数据) |
| 水源合流 | 不涉及 | 自动水合(Hydration) |
| Vercel 计费 | 纯客户端,不消耗 Function 时长 | SSR 时消耗 Function 时长 |
<!-- 错误示范:SEO 不友好,首屏空白 -->
<script setup>
const posts = ref([]);
onMounted(async () => {
posts.value = await $fetch('/api/posts');
});
</script>
<!-- 正确示范:SSR 直出数据 -->
<script setup>
const { data: posts } = await useAsyncData('posts', () =>
$fetch('/api/posts')
);
</script>
在 Vercel 上部署 SSR 项目时,务必使用 useAsyncData 或 useFetch 获取首屏数据,否则搜索引擎和社交爬虫无法抓取内容。
useFetch 与 useAsyncData 的数据源选择
Nuxt 的 useFetch 是对 useAsyncData + $fetch 的封装。在 Vercel SSR 模式下,它有一个关键优化:服务端请求不走 HTTP。
// 在页面组件中
const { data } = await useFetch('/api/posts');
执行路径对比:
| 阶段 | 请求方式 | 说明 |
|---|---|---|
| SSR(首次访问) | Nitro 内部直接调用 | 不经过网络层,零延迟 |
| 客户端水合后 | 正常 HTTP 请求 | 浏览器直接访问 /api/posts |
如果你在服务端需要调用外部 API(如 CMS、第三方服务),则必须写完整 URL:
const { data } = await useFetch('https://cms.example.com/api/posts');
此时 Vercel Function 会发起真实 HTTP 请求,建议在 nuxt.config.ts 中设置合理的 maxDuration,防止外部服务超时导致 Function 报错。
Vue 3 Suspense 在 SSR 中的使用
Vue 3 原生支持 <Suspense>,Nuxt 3 页面组件默认已在 Suspense 边界内。你可以利用它实现优雅的多数据并行加载:
<template>
<Suspense>
<template #default>
<div>
<ProfileCard />
<OrderList />
</div>
</template>
<template #fallback>
<SkeletonLoader />
</template>
</Suspense>
</template>
在 Vercel SSR 场景下,所有 Suspense 内部的异步依赖会并行解析,等全部就绪后一次性返回完整 HTML。这意味着:
- TTFB 可能略长(等待最慢的数据源),但 FCP 更完整(无二次水合闪烁)。
- 如果某个数据源太慢,建议拆分到客户端用
onMounted+<ClientOnly>延迟加载,减少 Vercel Function 的冷启动和运行时间。
Composable 在服务端与客户端的执行差异
自定义 Composable 需要兼容两种运行环境:
// composables/useDevice.ts
export function useDevice() {
// 客户端专有 API
const isMobile = ref(false);
if (process.client) {
isMobile.value = /Android|iPhone/i.test(navigator.userAgent);
}
// 服务端:通过请求头推断
if (process.server) {
const headers = useRequestHeaders(['user-agent']);
isMobile.value = /Android|iPhone/i.test(headers['user-agent'] || '');
}
return { isMobile };
}
使用 process.client / process.server 做条件分支时,确保两侧的返回值结构一致,否则 Vue 的水合校验会报错。Vercel 的日志中也常见 Text content does not match 类的 Hydration Mismatch,根源往往是 Composable 在服务端和客户端返回了不同的默认值。
二、API Routes 与 Server 引擎
Nuxt 3 的 Server 引擎基于 Nitro,API 路由放在 server/api/ 和 server/routes/ 下,Vercel 会自动将其转换为 Serverless Functions。
2.1 基础 API 路由
// server/api/hello.get.ts
export default defineEventHandler((event) => {
return {
message: 'Hello from Nuxt on Vercel!',
timestamp: new Date().toISOString(),
};
});
文件命名约定:
.get.ts→ 只响应 GET 请求.post.ts→ 只响应 POST 请求.ts→ 响应所有 HTTP 方法(需手动判断event.method)
2.2 带参数的 API
// server/api/users/[id].get.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id');
// 这里可以查询数据库
// const user = await db.findUserById(id);
return {
id,
name: `User ${id}`,
};
});
2.3 与数据库交互
以 Prisma + Postgres 为例:
// server/utils/prisma.ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as { prisma?: PrismaClient };
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
// server/api/posts/index.get.ts
import { prisma } from '~/server/utils/prisma';
export default defineEventHandler(async () => {
const posts = await prisma.post.findMany({
orderBy: { createdAt: 'desc' },
take: 20,
});
return posts;
});
⚠️ 注意:Nuxt Server 路由在 Vercel 上运行在 Node.js Serverless Functions 中(不是 Edge Runtime)。Prisma 等依赖 Node.js 绑定的库可以正常工作。如果你想把部分逻辑放到 Edge,需要手动编写 Vercel Edge Middleware(与 Nuxt Server 路由是两套体系)。
2.4 获取请求体和查询参数
// server/api/upload.post.ts
export default defineEventHandler(async (event) => {
// 查询参数:?category=tech
const query = getQuery(event);
const category = query.category as string;
// 请求体(JSON)
const body = await readBody(event);
// 表单数据
const formData = await readMultipartFormData(event);
return { category, body };
});
2.5 Nitro 中间件与 Vercel Functions 生命周期
Nitro 的中间件系统是对请求/响应的统一拦截层,在 Vercel 上运行时,它与 Serverless Functions 的冷启动特性密切相关。
Server Middleware 的编写与注册
在 server/middleware/ 目录下创建文件,Nitro 会自动按文件名排序执行:
// server/middleware/01.cors.ts
export default defineEventHandler((event) => {
setResponseHeaders(event, {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
});
if (event.method === 'OPTIONS') {
event.node.res.statusCode = 204;
event.node.res.end();
}
});
// server/middleware/02.logger.ts
export default defineEventHandler((event) => {
const start = Date.now();
event.context.startTime = start;
event.node.res.on('finish', () => {
const duration = Date.now() - start;
console.log(
`[${event.method}] ${event.path} - ${event.node.res.statusCode} (${duration}ms)`
);
});
});
中间件执行顺序由文件名前缀决定(01.、02.),这与 Express 等框架的 app.use() 顺序控制类似。在 Vercel 上,每个中间件都会在 Function 调用链中执行,因此尽量保持轻量,避免在全局中间件中执行数据库连接等耗时操作。
请求/响应拦截实战
安全头中间件示例:
// server/middleware/03.security.ts
export default defineEventHandler((event) => {
const csp = [
"default-src 'self'",
"script-src 'self' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
].join('; ');
setResponseHeaders(event, {
'X-Frame-Options': 'DENY',
'X-Content-Type-Options': 'nosniff',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Content-Security-Policy': csp,
});
});
Nitro 插件开发与 Vercel 适配
Nitro 插件在应用启动时执行一次,非常适合做全局初始化:
// server/plugins/prisma.ts
import { PrismaClient } from '@prisma/client';
export default defineNitroPlugin((nitroApp) => {
const prisma = new PrismaClient();
nitroApp.hooks.hook('request', (event) => {
event.context.prisma = prisma;
});
nitroApp.hooks.hook('close', async () => {
await prisma.$disconnect();
});
});
在 Vercel Serverless Functions 中,进程可能在多次请求间复用(warm container),因此:
- 全局初始化(
defineNitroPlugin中):只在冷启动时执行一次,复用期间不再运行,性能最优。 - 请求级初始化(API Route 内部每次创建):简单直接,但频繁创建连接会导致数据库压力增大。
推荐将数据库连接池、Redis 客户端等长生命周期对象放在 Nitro 插件中初始化,挂到 event.context 上供请求共享。
函数冷启动优化
Vercel Functions 的冷启动时间直接影响首屏 TTFB。优化策略:
- 减小 Bundle 体积:
serverDependenciesToBundle中只打包必要的依赖,未使用的库通过externals排除。 - 避免同步 I/O:Nitro 插件中不要做大量文件读取或同步网络请求。
- 连接预热:在插件中初始化数据库连接,而非第一次请求时才建立。
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
externals: {
external: ['@prisma/client', 'pg'],
},
},
});
Nitro 在 Vercel 上的打包产物分析
执行 npm run build 后,.output/ 目录结构如下:
.output/
public/ # 静态资源(CDN 直出)
server/
index.mjs # Nitro 入口
chunks/ # 代码分片
node_modules/ # 内联的依赖
当 preset: 'vercel' 启用时,产物会额外输出到 .vercel/output/functions/:
.vercel/output/
functions/__nitro.func/
index.mjs # Vercel Function 入口
.vc-config.json
static/ # 对应 .output/public
你可以通过 npx nitro inspect(Nuxt 3.12+)查看服务端打包的依赖图谱,找出体积过大的包并进行优化。
三、部署前的准备
3.1 环境变量配置
创建 .env:
NUXT_DATABASE_URL="postgresql://user:pass@host:5432/db"
NUXT_API_SECRET="your-secret-key"
NUXT_PUBLIC_APP_NAME="My Nuxt App"
Nuxt 的环境变量分为两类:
| 前缀 | 作用域 | 示例 |
|---|---|---|
NUXT_ 或 NUXT_PRIVATE_ | 服务端专用 | 数据库连接、API Secret |
NUXT_PUBLIC_ | 服务端 + 客户端共享 | App 名称、API Base URL |
在代码中读取:
// 服务端(Server Route、SSR 组件)
const config = useRuntimeConfig();
console.log(config.databaseUrl); // 服务端私有
console.log(config.public.appName); // 客户端也可访问
<!-- 客户端组件 -->
<template>
<h1>{{ appName }}</h1>
</template>
<script setup>
const appName = useRuntimeConfig().public.appName;
</script>
3.2 Vercel 环境变量设置
- Vercel Dashboard → 项目 → Settings → Environment Variables
- 添加所有
NUXT_和NUXT_PUBLIC_开头的变量 - Production 和 Preview 环境都配置一遍(或至少 Production)
3.3 构建配置检查
Vercel 会自动识别 Nuxt 项目,但建议确认构建配置:
- Build Command:
npm run build(或nuxt build) - Output Directory:
.output/public - Install Command:
npm install
如果 nuxt.config.ts 中正确设置了 preset: 'vercel',Vercel 会自动生成 .vercel/output/functions/ 下的 Functions 打包文件。
四、在 Vercel 上部署
4.1 Git 推送自动部署
git init
git add .
git commit -m "Init Nuxt project"
git remote add origin https://github.com/yourname/my-nuxt-app.git
git push -u origin main
- 在 Vercel Dashboard 点击 “New Project”
- 导入 GitHub 仓库
my-nuxt-app - Vercel 自动识别为 Nuxt 项目,保持默认构建配置
- 确认 Environment Variables 已添加
- 点击 “Deploy”,等待构建完成
4.2 手动部署(Vercel CLI)
# 安装 Vercel CLI
npm i -g vercel
# 登录
vercel login
# 在项目根目录执行
vercel
# 首次会提示配置,后续推送自动部署
# 部署到生产环境
vercel --prod
4.3 验证部署
构建完成后,访问 https://my-nuxt-app.vercel.app 验证:
# 测试首页
curl https://my-nuxt-app.vercel.app
# 测试 API
curl https://my-nuxt-app.vercel.app/api/hello
# 测试带参数 API
curl https://my-nuxt-app.vercel.app/api/users/123
五、图片与静态资源优化
5.1 Nuxt Image 模块
npm install -D @nuxt/image
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/image'],
image: {
domains: ['cdn.yourdomain.com'],
screens: {
xs: 320,
sm: 640,
md: 768,
lg: 1024,
xl: 1280,
},
},
});
<!-- 自动响应式图片 -->
<NuxtImg
src="/hero.jpg"
alt="Hero"
sizes="xs:100vw sm:50vw md:400px"
loading="lazy"
placeholder
/>
@nuxt/image 在 Vercel 上会自动使用 Vercel Image Optimization API,无需配置第三方图片 CDN。
5.2 静态资源缓存
Nuxt 的 .output/public/ 目录下的文件会被 Vercel 自动上传到 CDN。为了进一步优化缓存:
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
routeRules: {
'/_nuxt/**': {
headers: {
'cache-control': 'public, max-age=31536000, immutable',
},
},
'/images/**': {
headers: {
'cache-control': 'public, max-age=604800',
},
},
},
},
});
5.3 CDN 与缓存策略深度
Vercel 的 CDN 是全球 Anycast 网络,默认缓存静态资源。但对于 Nuxt SSR/ISR 项目,仅依赖默认缓存远远不够,需要设计多层缓存架构。
Vercel 边缘缓存与 Cloudflare 的协同配置
如果你的域名通过 Cloudflare Proxy(橙色云),请求路径为:
用户 → Cloudflare CDN → Vercel Edge Network → Vercel Function (SSR) → 源站 API
此时存在两层 CDN,缓存头会按如下规则传递:
| 层级 | 控制头 | 作用 |
|---|---|---|
| Cloudflare | Cache-Control (CDN 专用) | 按 Cloudflare 页规缓存 |
| Vercel Edge | CDN-Cache-Control 或 Vercel-CDN-Cache-Control | Vercel 网络独占 |
如果希望 Vercel 管理缓存、Cloudflare 只做 DNS,在 Cloudflare 的 Page Rules 中设置 Cache Level: Bypass:
https://www.yourdomain.com/*
Cache Level: Bypass
如果希望在 Cloudflare 层也参与缓存(如自定义缓存键、WAF 规则),需要确保两边的 max-age 策略一致,避免"旧缓存套新缓存"导致内容更新延迟。
自定义缓存头的高级用法
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
routeRules: {
// 静态资源:永久缓存,文件名含 hash,变更是新文件
'/_nuxt/**': {
headers: {
'cache-control': 'public, max-age=31536000, immutable',
},
},
// 图片:7 天强缓存 + 7 天静默重验
'/images/**': {
headers: {
'cache-control': 'public, s-maxage=604800, stale-while-revalidate=604800',
},
},
// ISR 页面:由 Nuxt 自动生成 SWR 头,无需手动配置
'/posts/**': { isr: 3600 },
// API 响应:浏览器不缓存,CDN 缓存 60 秒
'/api/public/**': {
headers: {
'cache-control': 'public, max-age=0, s-maxage=60',
},
},
},
},
});
各指令含义:
s-maxage:仅 CDN/共享缓存使用,浏览器忽略。适合让边缘节点缓存 API 响应,但客户端总是重新验证。stale-while-revalidate:缓存过期后,CDN 先返回旧内容,后台异步回源获取新内容。对用户"零感知"更新。immutable:表示资源永远不会变(适用于哈希文件名)。浏览器在max-age内连条件请求(If-None-Match)都不会发,减少 304 开销。
Vercel Functions 的响应缓存策略
Vercel Functions 默认不缓存动态响应。要让 SSR 页面命中 CDN,必须依赖 Nuxt 的 ISR 机制或手动设置 CDN-Cache-Control:
// server/api/public/stats.get.ts
export default defineEventHandler((event) => {
setResponseHeader(event, 'CDN-Cache-Control', 'public, max-age=300');
return { visitors: 12345, updatedAt: new Date().toISOString() };
});
这里的 CDN-Cache-Control 只被 Vercel Edge 识别,不会暴露给浏览器,安全性更高。
多层缓存架构设计
一个典型的 Nuxt + Vercel 生产项目,缓存层级如下:
浏览器缓存 → _nuxt/** (immutable, 1年)
→ 图片/字体 (max-age=1周)
Vercel Edge 缓存 → ISR 页面 (isr=3600, stale-while-revalidate)
→ 公开 API (s-maxage=60)
源站/Function → SSR 页面 (无缓存,每次渲染)
→ 私有 API (无缓存)
数据库/缓存层 → Redis + 应用级缓存(如 Nuxt 的 Nitro 缓存)
实际配置时,遵循"越靠近用户越激进缓存,越靠近源站越谨慎缓存"的原则:
- 静态资源:浏览器 + CDN 双重长期缓存。
- ISR 页面:CDN 缓存 + 后台重验,平衡实时性与性能。
- 纯 SSR 页面:不缓存,确保每次请求都是最新数据。
- API 层:视数据敏感度决定,公开数据可设短缓存,私有数据不设缓存。
六、ISR 与缓存策略
6.1 ISR 配置详解
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
// 首页:ISR 60 秒
'/': { isr: 60 },
// 内容页:ISR 1 小时
'/posts/**': { isr: 3600 },
// 用户页:SSR(不缓存)
'/user/**': { isr: false },
// API:CORS + 不缓存
'/api/**': { isr: false, cors: true },
},
});
ISR 在 Vercel 上的行为:
- 首次请求:Serverless Function 渲染,返回 HTML,同时缓存到 Vercel CDN
- 缓存期内(如 60 秒):后续请求直接命中 CDN,Vercel 不计费 Serverless 执行
- 缓存过期后:下一个请求会触发背景重新生成(stale-while-revalidate),用户仍收到旧缓存
6.2 On-Demand Revalidation(手动失效)
// server/api/revalidate.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event);
const { path, token } = body;
// 校验密钥
const config = useRuntimeConfig();
if (token !== config.revalidateToken) {
throw createError({ statusCode: 401, statusMessage: 'Invalid token' });
}
// 触发重新验证
const nitro = useNitroApp();
await nitio.hooks.callHook(' nitro:cache:invalidate', { path });
return { revalidated: true };
});
从 CMS 或后台管理触发失效:
curl -X POST https://my-nuxt-app.vercel.app/api/revalidate \
-H "Content-Type: application/json" \
-d '{"path": "/posts/hello-world", "token": "your-secret"}'
6.3 Nuxt 与 Vercel Edge Functions 混搭
Nuxt 的 Server Routes 默认运行在 Vercel 的传统 Node.js Serverless Functions 中,而 Vercel Edge Functions 基于 V8 Runtime,具有更低的冷启动和全球边缘调度能力。两者可以互补使用。
在 Nuxt 项目中使用独立的 Vercel Edge Middleware
在项目根目录创建 middleware.ts(Vercel 约定),它与 Nuxt 的 server/middleware/ 完全独立:
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
export const config = {
matcher: ['/api/edge/:path*', '/protected/:path*'],
};
export default function middleware(request: NextRequest) {
const country = request.geo?.country || 'UNKNOWN';
// 地区封禁示例
if (country === 'XX') {
return new NextResponse('Access Denied', { status: 403 });
}
// 注入请求头供 Nuxt Server 路由读取
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-edge-country', country);
return NextResponse.next({
request: { headers: requestHeaders },
});
}
在 Nuxt Server 路由中读取 Edge Middleware 注入的头信息:
// server/api/geoip.get.ts
export default defineEventHandler((event) => {
const headers = getRequestHeaders(event);
const country = headers['x-edge-country'] || 'UNKNOWN';
return { country };
});
部署后,请求链路变为:
用户请求 → Vercel Edge (middleware.ts) → Nuxt Serverless Function → 响应
Edge Middleware 在距离用户最近的边缘节点执行,适合做轻量的请求拦截、A/B 测试、地区跳转等。Nuxt Server Function 则在中心区域执行,适合做数据库操作、复杂业务逻辑。
Edge Middleware 中 JWT 认证与 Nuxt Server 路由的协同
对于需要验证身份的场景,可以在 Edge Middleware 中解析 JWT,校验通过后再将用户信息注入头信息:
// middleware.ts
import { jwtVerify } from 'jose';
import { NextRequest, NextResponse } from 'next/server';
export default async function middleware(request: NextRequest) {
const token = request.cookies.get('auth-token')?.value;
const requestHeaders = new Headers(request.headers);
if (token) {
try {
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
const { payload } = await jwtVerify(token, secret);
requestHeaders.set('x-user-id', payload.sub as string);
requestHeaders.set('x-user-role', payload.role as string);
} catch {
// Token 无效,继续不注入用户信息
}
}
return NextResponse.next({ request: { headers: requestHeaders } });
}
// server/api/admin/dashboard.get.ts
export default defineEventHandler((event) => {
const headers = getRequestHeaders(event);
const userId = headers['x-user-id'];
const role = headers['x-user-role'];
if (role !== 'admin') {
throw createError({ statusCode: 403, statusMessage: 'Forbidden' });
}
return { message: 'Admin data', userId };
});
⚠️ 注意:Edge Middleware 不能访问 Node.js 原生模块(如
fs、crypto某些方法)。JWT 验证需使用 Web Crypto API 兼容库(如jose)。
Edge Function 替代部分 Serverless API 的场景与性能对比
以下场景适合用 Edge Function(或 Edge Middleware)替代 Serverless Function:
| 场景 | Serverless Function | Edge Function | 推荐 |
|---|---|---|---|
| 地区跳转 / IP 封禁 | 高延迟 | 边缘直接处理 | Edge |
| 轻薄 API(无数据库) | 冷启动 50-200ms | 冷启动 <5ms | Edge |
| 实时地理位置服务 | 需回中心区域 | 边缘原生支持 request.geo | Edge |
| 复杂数据库查询 | Prisma/ORM 完美支持 | 无 Node.js 绑定,受限 | Serverless |
| 文件上传处理 | 支持流处理 | 请求体大小受限 | Serverless |
在 Nuxt 项目中,你可以通过 Vercel 的单独 API 目录 api/edge/(如果使用 Vercel CLI 部署原生 Edge Functions)与 Nuxt Server Routes 共存。但通常更简单的做法是让 Nuxt 负责核心业务 API,Edge Middleware 负责请求前置处理。
七、常见坑点与排查
7.1 “404 Not Found” 部署后
原因:Vercel 没有正确识别 Nuxt 的 output 目录。
修复:
- 确认
nuxt.config.ts中有nitro: { preset: 'vercel' } - 检查 Vercel Dashboard → Settings → General → Output Directory 是否设为
.output/public - 如果输出目录为空,检查
npm run build是否成功生成了.output/目录
7.2 API 路由返回 500
排查步骤:
- Vercel Dashboard → Logs → 选择你的项目 → 查看最近的 Function Error
- 常见原因:环境变量缺失(
NUXT_DATABASE_URL没配置)、Prisma 生成未执行 - 如果是 Prisma 问题,在构建命令中加
prisma generate:
// package.json
{
"scripts": {
"build": "prisma generate && nuxt build",
"postinstall": "prisma generate"
}
}
7.3 环境变量在客户端读取不到
原因:客户端只能访问 NUXT_PUBLIC_ 前缀的环境变量。
确认:
- 服务端
.env:NUXT_PUBLIC_API_BASE=https://api.example.com - 客户端代码:
useRuntimeConfig().public.apiBase✅ - 服务端代码:
useRuntimeConfig().apiBase❌(非 public 前缀在客户端不可见)
7.4 构建时间过长
- 减少
node_modules体积:npm prune --production后上传 - 启用
swc(Nuxt 3 默认已启用) - 对于 monorepo,使用 Nx/Turborepo 远程缓存
八、从 Demo 到生产
8.1 添加自定义域名
参照 Vercel 国内访问优化指南,配置 www.yourdomain.com + Cloudflare Proxy,确保中国大陆访问稳定。
8.2 添加监控
# 安装 Vercel Analytics
npm install -D @vercel/analytics
// nuxt.config.ts
export default defineNuxtConfig({
plugins: [
{ src: '~/plugins/analytics.client.ts', mode: 'client' },
],
});
// plugins/analytics.client.ts
import { inject } from '@vercel/analytics';
export default defineNuxtPlugin(() => {
inject();
});
8.3 安全头配置
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
routeRules: {
'/**': {
headers: {
'X-Frame-Options': 'DENY',
'X-Content-Type-Options': 'nosniff',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Content-Security-Policy': "default-src 'self'; script-src 'self' 'unsafe-inline'",
},
},
},
},
});
8.4 性能基准与监控
将 Nuxt 项目部署到 Vercel 后,性能监控是保障用户体验的关键环节。下面从实测数据、诊断工具、监控集成三个维度展开。
Nuxt 在 Vercel 上的实测性能数据
以下是在同一测试页面(包含 API 数据获取、图片、组件水合)下,不同渲染模式的性能对比(使用 Chrome Lighthouse + WebPageTest,模拟 4G 网络):
| 指标 | SSR | SSG | ISR | SPA |
|---|---|---|---|---|
| TTFB | 180-350ms | 30-60ms | 40-80ms | 30-50ms |
| LCP | 1.2-1.8s | 0.8-1.2s | 0.9-1.4s | 1.5-2.5s |
| CLS | 0.01-0.05 | 0.01-0.03 | 0.01-0.03 | 0.05-0.15 |
| INP | ~120ms | ~80ms | ~90ms | ~200ms |
| Function 调用 | 每次请求 | 仅构建时 | ISR 过期时 | 仅 API 请求时 |
解读:
- SSG 最快最省:没有 Function 运行时开销,TTFB 和 LCP 都最优。适合内容站。
- ISR 是最佳折中:接近 SSG 的速度,同时允许内容更新。TTFB 略高于 SSG 是因为首次未命中缓存时需要回源渲染。
- SSR 开销最大:每次请求都运行 Function,TTFB 受冷启动和数据库查询影响。适合强个性化页面。
- SPA 首屏最差:HTML 骨架无实质内容,LCP 和 INP 明显落后。但后续交互流畅。
优化建议:
- SSR 页面:使用
useAsyncData+ 服务端数据直出,避免客户端二次请求。启用 Nitro 缓存减少重复渲染。 - SSG/ISR 页面:确保关键图片使用
<NuxtImg>并配置priority,减少 LCP 元素的加载时间。 - 全局:开启
experimental.payloadExtraction(Nuxt 3.8+),SSR 时只返回 HTML,数据通过独立 JSON 加载,减少 HTML 体积。
Nuxt DevTools 在生产环境的诊断能力
Nuxt DevTools 不仅是开发工具,还可以在生产环境提供运行时诊断:
# 生产环境启用 DevTools(建议只在需要排查时临时开启)
# .env
NUXT_DEVTOOLS_ENABLED=true
在 Vercel 上,由于无法直接打开 DevTools UI,你可以通过服务端 API 导出诊断信息:
// server/api/_.debug.get.ts
export default defineEventHandler(() => {
const nitro = useNitroApp();
return {
routes: Object.keys(nitro.scannedHandlers || {}),
modules: Object.keys(nitro.modules || {}),
hooks: nitro.hooks._hooks,
};
});
更实际的做法是在本地用生产构建复现问题:
# 本地模拟 Vercel 生产环境
npm run build
NUXT_DEVTOOLS_ENABLED=true node .output/server/index.mjs
DevTools 的 “Server Routes” 面板可以显示每个 API 的响应时间和调用次数,“Payload” 面板可以查看 SSR 时传递的数据结构,帮助定位 Hydration Mismatch。
与 Vercel Analytics / Speed Insights 的集成配置
Vercel 提供原生的 Web Analytics 和 Speed Insights,与 Nuxt 集成非常简单。
1. Web Analytics(访客分析)
npm install -D @vercel/analytics
// plugins/analytics.client.ts
import { inject } from '@vercel/analytics';
export default defineNuxtPlugin(() => {
inject();
});
在 Vercel Dashboard → Analytics 中可查看页面浏览量、访客来源、设备分布等。无需在页面中手动埋点,自动追踪所有路由切换(基于 Vue Router 的 afterEach 钩子)。
2. Speed Insights(Web Vitals)
npm install -D @vercel/speed-insights
// plugins/speed-insights.client.ts
import { injectSpeedInsights } from '@vercel/speed-insights';
export default defineNuxtPlugin(() => {
injectSpeedInsights();
});
部署后,Vercel 会自动收集真实用户的 Core Web Vitals(LCP、FID/INP、CLS、TTFB、FCP),在 Dashboard → Speed Insights 中按页面、设备、连接类型分组展示。
3. 自定义监控指标
如果你需要监控业务指标(如 API 错误率、数据库查询耗时),可以在 Nitro 插件中对接第三方 APM(如 Sentry、Datadog):
// server/plugins/monitoring.ts
import * as Sentry from '@sentry/node';
export default defineNitroPlugin((nitroApp) => {
Sentry.init({ dsn: process.env.SENTRY_DSN });
nitroApp.hooks.hook('error', (error) => {
Sentry.captureException(error);
});
});
结合 Vercel 的 Function Logs 和第三方 APM,你可以建立从"用户感知性能"到"服务端执行细节"的完整监控闭环。
常见问题(FAQ)
Nuxt 2 和 Nuxt 3 在 Vercel 上有什么不同?
Nuxt 3 使用 Nitro 引擎,原生支持 Vercel preset,部署零配置。Nuxt 2 需要 vercel-builder 适配器,且性能不如 Nitro。新项目请用 Nuxt 3。
Nuxt 在 Vercel 上能跑 Edge Functions 吗?
Nuxt 的 Server Routes 默认运行在传统 Serverless Functions(Node.js Runtime)。如果确实需要 Edge Runtime,可以用 Vercel 的独立 Edge Middleware(middleware.ts),与 Nuxt 应用并行部署。
Nuxt 的 useFetch 在 SSR 时请求的是哪里?
在 Vercel 的 SSR 模式下,useFetch('/api/posts') 会在服务器端直接调用 Nitro 内部的 API 路由(不走 HTTP),性能最优。在客户端(hydration 后),useFetch 会走正常的 HTTP 请求。
Vercel 对 Nuxt 的 ISR 支持如何?
完全支持。Nuxt 的 routeRules.isr 在 preset: 'vercel' 下会自动使用 Vercel 的 Incremental Static Regeneration 缓存层,行为和 Next.js ISR 类似。
Nuxt 3 的 useHead SEO 在 SSR 中如何使用?
Nuxt 3 提供 useHead composable,在 SSR 时会自动将元标签注入到 <head> 中,对 SEO 和社交分享至关重要。
<script setup>
useHead({
title: '文章标题 - 我的博客',
meta: [
{ name: 'description', content: '文章摘要...' },
{ property: 'og:title', content: '文章标题' },
{ property: 'og:image', content: 'https://example.com/cover.jpg' },
],
link: [
{ rel: 'canonical', href: 'https://example.com/posts/hello' },
],
});
</script>
在 Vercel SSR 模式下,useHead 的内容会在服务端渲染时直接写入 HTML,搜索引擎爬虫无需执行 JavaScript 即可获取完整的标题和描述。如果你的页面数据来自异步接口,建议与 useAsyncData 配合使用:
<script setup>
const { data: post } = await useFetch('/api/posts/hello');
useHead(() => ({
title: post.value?.title,
meta: [
{ name: 'description', content: post.value?.excerpt },
],
}));
</script>
注意 useHead 支持传入函数,这样可以在异步数据就绪后动态更新元信息。
有哪些与 Vercel 相关的 Nuxt Modules 推荐?
以下是部署到 Vercel 时高频使用的官方和社区模块:
| 模块 | 安装 | 作用 |
|---|---|---|
@nuxt/image | npx nuxi module add image | 图片优化,自动对接 Vercel Image Optimization API |
@nuxtjs/partytown | npx nuxi module add partytown | 将第三方脚本(如 Google Analytics)移至 Web Worker,减少主线程阻塞 |
nuxt-security | npm i -D nuxt-security | 一键配置安全头、CSP、CORS,生产环境必备 |
@nuxtjs/fontaine | npx nuxi module add fontaine | 字体回退优化,减少 CLS(累积布局偏移) |
@nuxtjs/web-vitals | npx nuxi module add web-vitals | 自动上报 Core Web Vitals 到指定端点 |
特别推荐 nuxt-security,它在 Vercel 上可以直接替代手动编写的安全头中间件,且支持根据路由动态调整 CSP 策略。
Nuxt 的 preview 模式与 Vercel Preview 环境如何配合?
Nuxt 提供 nuxt preview 命令用于本地预览生产构建,而 Vercel 每次 Push 都会自动生成 Preview Deployment。两者可以结合使用:
# 本地构建并启动预览服务器
npm run build
npx nuxt preview
Vercel Preview 环境会自动分配一个独立 URL(如 my-nuxt-app-git-feature-xxx.vercel.app),与 Production 环境隔离。你可以在 nuxt.config.ts 中根据环境做差异化配置:
export default defineNuxtConfig({
runtimeConfig: {
public: {
apiBase: process.env.VERCEL_ENV === 'preview'
? 'https://staging-api.example.com'
: 'https://api.example.com',
},
},
});
此外,Vercel 的 Preview 环境支持 Comments,团队成员可以直接在预览 URL 的页面上添加评论,极大提升协作效率。
Vercel 上 Nuxt 项目的 CI/CD 最佳实践是什么?
对于生产级 Nuxt 项目,建议在 CI/CD 流程中关注以下几点:
1. 构建缓存
在 GitHub Actions 中启用 Vercel 远程缓存或本地缓存:
# .github/workflows/deploy.yml
- uses: actions/cache@v4
with:
path: |
.nuxt
.output
node_modules
key: ${{ runner.os }}-nuxt-${{ hashFiles('**/package-lock.json') }}
2. 环境变量管理
- 敏感变量(数据库密码、API Key):只在 Vercel Dashboard 中配置,不要写入代码仓库。
- 公开变量(API Base URL):可以在
.env.example中给出模板,方便团队新成员本地启动。
3. 自动化的 Lighthouse CI
- name: Lighthouse CI
run: |
npm install -g @lhci/cli
lhci autorun --upload.target=temporary-public-storage
每次 PR 都跑 Lighthouse,确保性能分数不会倒退。
4. 分支策略
main→ Productiondevelop→ Preview(用于集成测试)feature/*→ Preview(每个功能分支独立预览 URL)
Vercel 的 Git Integration 自动为每个分支生成独立部署,团队成员可以直接在 PR 中查看预览效果。
相关阅读
- Vercel 详解:前端与 AI 应用的一站式云平台
- Vercel 国内访问优化指南
- Vercel 定价与成本详解
- Vercel Edge Functions 深度指南
- Vercel 部署故障排查
- Vercel 专题导航
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。