本文目录导读:
这是一个非常经典且关键的全栈问题,处理跨域(CORS,跨源资源共享)是全栈开发中必不可少的一环,不同的全栈框架有不同的处理方式,但核心思路都是在服务端设置正确的响应头,或者通过代理转发请求。
下面我会从原理和主流框架的实践两个方面来解答。
核心原理:服务端设置 CORS 响应头
浏览器出于安全考虑,限制了跨源 HTTP 请求,要解决这个问题,关键在于服务端返回的响应中包含特定的 HTTP 头,告诉浏览器“这个请求是允许的”。
最关键的几个响应头是:
Access-Control-Allow-Origin:允许哪些域名访问( 表示所有域名,或指定具体域名)。Access-Control-Allow-Methods:允许哪些 HTTP 方法(GET、POST、PUT、DELETE 等)。Access-Control-Allow-Headers:允许哪些自定义请求头。Access-Control-Allow-Credentials:是否允许携带 Cookie(设为true时,Allow-Origin不能为 )。Access-Control-Max-Age:预检请求(OPTIONS)的有效期。
简单请求(如 GET、POST 且 Content-Type 为 text/plain 等)浏览器直接发请求,看响应头。
非简单请求(如 PUT、DELETE 或自定义头)浏览器会先发一个 OPTIONS 预检请求,确认服务端允许后,再发真实请求。
主流全栈框架的处理方式
传统 SSR 框架(如 Next.js、Nuxt.js、Remix)
这类框架通常在服务端渲染页面,API 路由也在同一域名下,开发时的跨域问题主要出在前端开发服务器(如 localhost:3000)请求后端 API(如 localhost:4000)。
-
在 API 路由中手动设置 CORS(生产环境) 在服务端(如 Next.js API 路由、Nuxt 的 server 目录)中,自己设置响应头。
// Next.js API Route (pages/api/hello.ts) import { NextApiRequest, NextApiResponse } from 'next' export default function handler(req: NextApiRequest, res: NextApiResponse) { // 设置 CORS 头 res.setHeader('Access-Control-Allow-Origin', 'https://your-frontend-domain.com') res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE') res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization') // 处理 OPTIONS 预检请求 if (req.method === 'OPTIONS') { return res.status(200).end() } res.status(200).json({ message: 'Hello' }) } -
使用
cors中间件(推荐) 这是最简洁的方式,在 Next.js 或 Express 中,直接使用cors包。// Next.js API Route (app/api/hello/route.ts) import { NextRequest, NextResponse } from 'next/server' import Cors from 'cors' // 初始化 cors 中间件 const cors = Cors({ origin: 'https://your-frontend-domain.com', methods: ['GET', 'POST'], }) export async function GET(request: NextRequest) { // 执行中间件(Next.js 中需要手动运行) await new Promise((resolve, reject) => { cors(request as any, {} as any, (result) => { if (result instanceof Error) reject(result) resolve(result) }) }) return NextResponse.json({ message: 'Hello' }) } -
开发时使用代理(最常用) 在
next.config.js或vite.config.ts中配置代理,让前端开发服务器把/api请求转发到后端,从而不触发跨域。// next.config.js module.exports = { async rewrites() { return [ { source: '/api/:path*', destination: 'http://localhost:4000/api/:path*', // 代理到后端 }, ] }, }// vite.config.ts (Nuxt 类似) export default defineConfig({ server: { proxy: { '/api': { target: 'http://localhost:4000', changeOrigin: true, }, }, }, })
全栈语言框架(Next.js、Nuxt、SvelteKit、Remix 等)
这些框架的全栈特性意味着你可以在前端页面里直接写后端代码(如 getServerSideProps、load 函数、+page.server.ts 等),这些代码运行在服务端,服务端发起 HTTP 请求没有跨域问题。
<!-- SvelteKit +page.server.ts -->
import { error } from '@sveltejs/kit'
/** @type {import('./$types').PageServerLoad} */
export async function load({ fetch }) {
// 这个 fetch 运行在服务端,不存在跨域
const res = await fetch('http://internal-api.example.com/data')
if (!res.ok) throw error(500, 'API 请求失败')
return { items: await res.json() }
}
这种方式的优点是避免了 CORS 配置,但缺点是需要后端 API 对服务端环境是可达的(内网或同一网络)。
Node.js + Express / Koa 后端框架
这是最标准的后端处理方式,直接使用 cors 中间件挂载到全局或特定路由。
// Express 后端
const express = require('express')
const cors = require('cors')
const app = express()
// 允许所有来源(开发用,生产环境要指定域名)
app.use(cors({
origin: 'https://your-frontend.com',
credentials: true, // 允许携带 Cookie
}))
// 或者只为一个路由启用
// app.get('/api/user', cors(), (req, res) => { ... })
app.listen(3001)
BFF(Backend For Frontend)模式
这种模式专门解决跨域和 API 聚合问题,BFF 层位于前端和后端服务之间,前端只请求 BFF,BFF 向后端服务发送请求(无跨域),再将结果返回前端(BFF 自己设置 CORS)。
// 一个简单的 BFF 服务
import express from 'express'
import cors from 'cors'
import axios from 'axios'
const app = express()
app.use(cors({ origin: 'http://localhost:5173' })) // 只允许前端域名
app.get('/api/user', async (req, res) => {
const response = await axios.get('http://internal-backend:4000/user')
res.json(response.data)
})
最佳实践建议
| 场景 | 推荐方案 |
|---|---|
| 生产环境 | 后端服务(API 层)明确配置 CORS,只允许受信任的前端域名,不要用 。 |
| 开发环境 | 使用代理(Vite、Next.js rewrites、webpack-dev-server),简单且不影响生产配置。 |
| 想要彻底避免 CORS | 在服务端渲染/加载数据(如 getServerSideProps、load 函数),请求走服务端。 |
| 微服务架构 | 使用 BFF 模式,前端只请求 BFF,BFF 负责聚合下游服务。 |
| 需要携带 Cookie | 服务端 CORS 必须指定具体域名(不能是 ),且 credentials: true。 |
一句话总结:尽量让前端请求走服务端代码或同源代理,如果不行,就用 CORS 中间件精确控制允许的来源和方法。
标签: 同源策略