[Nuxt 4 实战] 搞定跨域与密钥安全:Nuxt 服务端路由(Nitro)的妙用

13 阅读3分钟

前言

在开发 SonicToolLab 的“汇率转换”和“每日一言”工具时,我遇到了典型的“前端困境”:

  1. 跨域报错 (CORS): 直接在 Vue 组件里 fetch('https://some-api.com'),浏览器直接报红,因为对方服务器不允许跨域。
  2. 裸奔的 Key: 如果我想调用 OpenAI 或其他收费 API,把 Key 写在前端代码里,无异于把钱包交给路人。

Nuxt 4 不仅仅是一个前端框架,它内置的 Nitro 引擎让它同时具备了后端能力。今天就来聊聊如何用 server/api 充当我们的“中间人”。

🛡️ 1. 原理:为什么需要中间层?

浏览器的同源策略限制了跨域,但服务器与服务器之间是没有跨域限制的

  • 错误做法: 浏览器 -> 外部 API (被拦截 ❌)
  • 正确做法: 浏览器 -> Nuxt Server (同源 ✅) -> 外部 API (服务器请求 ✅) -> 返回数据

在 Nuxt 中,不需要配置复杂的 Nginx,只需要在 server/api 目录下新建文件即可。

🔐 2. 实战:隐藏你的 API Key

假设我们要开发一个“天气查询”工具,需要用到第三方的 API Key。

第一步:配置环境变量

千万不要把 Key 硬编码在代码里!在项目根目录创建 .env 文件:

# .env
NUXT_WEATHER_API_KEY=your_secret_key_here

然后在 nuxt.config.ts 中暴露它(注意:只在 server 端暴露,不在 public 中暴露):

TypeScript

// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    // 私有配置,只能在服务端获取
    weatherApiKey: '', 
    // public: { ... } // 公开配置,前端可以获取
  }
})

第二步:编写服务端接口

创建 server/api/weather.get.ts。这个文件会自动映射为 /api/weather 路由。

TypeScript

// server/api/weather.get.ts
export default defineEventHandler(async (event) => {
  // 1. 获取前端传来的参数
  const query = getQuery(event)
  const city = query.city || 'Beijing'

  // 2. 获取配置中的 Key
  const config = useRuntimeConfig()
  const apiKey = config.weatherApiKey

  // 3. 服务端发起请求(这里可以使用 $fetch)
  try {
    const data = await $fetch(`https://api.thirdparty.com/v1/weather`, {
      params: {
        q: city,
        key: apiKey // Key 在这里使用,前端永远看不到
      }
    })
    return data
  } catch (e) {
    throw createError({
      statusCode: 500,
      statusMessage: '天气服务暂时不可用'
    })
  }
})

第三步:前端调用

在 Vue 页面中,我们可以像调用本地函数一样调用这个 API:

Code snippet

<script setup>
const { data } = await useFetch('/api/weather', {
  params: { city: 'Shanghai' }
})
</script>

效果: 用户在浏览器控制台的网络请求中,只能看到发往 /api/weather 的请求,完全看不到第三方的真实 URL 和那个珍贵的 API Key。

⚡️ 3. 进阶:利用缓存节省配额 (Cached Event Handler)

独立开发者的 API 额度通常有限(比如免费版一天只能调 1000 次)。如果用户频繁刷新页面,额度很快就用完了。

Nuxt 4 (Nitro) 提供了一个神级功能:服务端缓存

我们只需要把 defineEventHandler 改为 defineCachedEventHandler

TypeScript

// server/api/currency.get.ts
export default defineCachedEventHandler(async (event) => {
  const config = useRuntimeConfig()
  // 请求汇率接口
  const data = await $fetch('[https://api.exchangerate.com/v1/latest](https://api.exchangerate.com/v1/latest)', {
    headers: { Authorization: config.currencyKey }
  })
  return data
}, {
  // 🔥 核心配置
  maxAge: 60 * 60, // 缓存 1 小时
  name: 'currency-rates',
  getKey: (event) => 'global-rates' // 缓存键名
})

发生了什么?

  1. 第一个用户请求接口,Nuxt 服务器去第三方拉取数据,存入缓存,返回给用户。
  2. 接下来的 1 小时内,无论有多少用户访问,Nuxt 直接返回缓存的数据不会向第三方发起任何请求。
  3. 这对于汇率、天气这种不需要秒级更新的数据,简直是省钱神器!

⚠️ 4. 避坑指南:Cloudflare 环境下的 Fetch

如果你像我一样部署在 Cloudflare Pages,底层的 Node.js 环境其实是 workerd (V8 Isolate)。

在极少数情况下,某些 Node 原生模块(如 axios 的某些适配器)可能不兼容。

建议: 在 Nuxt 服务端接口中,始终使用 Nuxt 内置的 $fetch 工具,它针对各种运行时(Node, Deno, Cloudflare Workers)都做了完美兼容。

总结

通过 Nuxt 4 的服务端路由,我们不需要额外部署一个后端服务(Go/Python/Java),就能在一个项目中解决:

  1. 跨域代理
  2. 隐藏 API Key
  3. 接口缓存

这让 SonicToolLab 这样的纯前端项目,也能拥有处理复杂业务的能力。

下一篇,我们将进入“优化篇”,聊聊 《Nuxt 4 项目结构优化:如何自动导入与模块化管理,告别满屏 import》

👉 SonicToolLab 在线体验