了解了CORS的基本原理后,让我们聚焦于如何在Next.js项目中实际解决跨域问题。Next.js提供了多种方式来处理CORS,具体方法取决于你使用的是Pages Router还是App Router,以及特定的应用场景。
针对不同Next.js API的CORS实现
1. Pages Router: API Routes
在Pages Router中,API Routes位于pages/api目录下,需要手动设置CORS头。
基本实现:
// pages/api/data.js
export default function handler(req, res) {
// 设置CORS头
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// 处理OPTIONS预检请求
if (req.method === 'OPTIONS') {
res.status(200).end();
return;
}
// 正常处理请求
if (req.method === 'GET') {
res.status(200).json({ message: "Success" });
} else {
res.status(405).json({ message: "Method not allowed" });
}
}
使用cors中间件包:
// pages/api/data.js
import Cors from 'cors';
// 初始化CORS中间件
const cors = Cors({
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
origin: '*',
credentials: true,
});
// 辅助函数执行中间件
function runMiddleware(req, res, fn) {
return new Promise((resolve, reject) => {
fn(req, res, (result) => {
if (result instanceof Error) {
return reject(result);
}
return resolve(result);
});
});
}
export default async function handler(req, res) {
// 运行CORS中间件
await runMiddleware(req, res, cors);
// 处理请求
res.status(200).json({ message: "Success" });
}
2. App Router: Route Handlers
App Router引入的Route Handlers使用新的响应API,位于app目录中的route.js|ts文件。
基本实现:
// app/api/data/route.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export async function GET(request: NextRequest) {
const response = NextResponse.json({ message: "Success" });
// 设置CORS头
response.headers.set('Access-Control-Allow-Origin', '*');
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
return response;
}
// 处理OPTIONS请求
export async function OPTIONS(request: NextRequest) {
const response = new NextResponse(null, { status: 204 });
response.headers.set('Access-Control-Allow-Origin', '*');
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
response.headers.set('Access-Control-Max-Age', '86400'); // 缓存预检结果24小时
return response;
}
多端点复用的辅助函数:
// app/lib/cors.ts
import { NextResponse } from 'next/server';
export function setCorsHeaders(response: NextResponse) {
response.headers.set('Access-Control-Allow-Origin', '*');
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
return response;
}
// app/api/items/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { setCorsHeaders } from '@/app/lib/cors';
export async function GET(request: NextRequest) {
const response = NextResponse.json({ items: ['item1', 'item2'] });
return setCorsHeaders(response);
}
export async function OPTIONS(request: NextRequest) {
const response = new NextResponse(null, { status: 204 });
setCorsHeaders(response);
response.headers.set('Access-Control-Max-Age', '86400');
return response;
}
3. Middleware全局CORS处理
使用Middleware是处理CORS的最灵活方式,它可以应用于整个应用或特定路径,不需要在每个API路由中重复代码。
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// 只处理API路由的请求
if (request.nextUrl.pathname.startsWith('/api/')) {
// 获取请求的来源
const origin = request.headers.get('origin') || '';
// 创建响应对象
const response = NextResponse.next();
// 设置CORS头
response.headers.set('Access-Control-Allow-Origin', origin || '*');
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
response.headers.set('Access-Control-Max-Age', '86400');
// 处理OPTIONS请求
if (request.method === 'OPTIONS') {
return new NextResponse(null, {
status: 204,
headers: response.headers,
});
}
return response;
}
return NextResponse.next();
}
// 配置哪些路径触发中间件
export const config = {
matcher: '/api/:path*',
};
4. Server Actions与CORS
Next.js 13引入的Server Actions也需要考虑CORS,特别是当从其他域调用时。
Server Actions默认创建公共HTTP端点,可以通过serverActions.allowedOrigins配置允许的来源:
// next.config.js
/** @type {import('next').NextConfig} */
module.exports = {
experimental: {
serverActions: {
allowedOrigins: ['example.com', '*.example.com'],
},
},
}
Server Actions使用POST方法,并通过比较Origin和Host头提供额外保护。确保在Server Action中添加适当的授权检查:
// app/actions.ts
'use server'
import { auth } from './lib';
export async function addItem() {
// 验证用户是否有权限
const { user } = auth();
if (!user) {
throw new Error('Authentication required');
}
// 执行操作...
}
常见CORS场景解决方案
1. 处理带凭证的请求
当需要跨域发送Cookie时:
- 服务器必须指定具体的允许域名(不能用
*) - 必须设置
Access-Control-Allow-Credentials: true - 客户端必须设置
credentials: 'include'
// app/api/auth/route.ts
import { cookies } from 'next/headers';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
// 身份验证逻辑...
// 设置cookie
const cookieStore = cookies();
cookieStore.set('session', 'token123', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 60 * 24 * 7,
path: '/',
});
const response = NextResponse.json({ success: true });
// 设置CORS头(注意使用具体域名)
response.headers.set('Access-Control-Allow-Origin', 'https://example.com');
response.headers.set('Access-Control-Allow-Credentials', 'true');
response.headers.set('Access-Control-Allow-Methods', 'POST, OPTIONS');
response.headers.set('Access-Control-Allow-Headers', 'Content-Type');
return response;
}
// 客户端代码需要设置 credentials: 'include'
// fetch('https://api.myapp.com/api/auth', {
// method: 'POST',
// credentials: 'include',
// ...
// })
2. 动态设置允许的来源
在生产环境中,通常需要限制允许的来源域名:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
// 允许的来源列表
const allowedOrigins = [
'https://example.com',
'https://www.example.com',
'https://app.example.com',
];
// 开发环境添加localhost
if (process.env.NODE_ENV === 'development') {
allowedOrigins.push('http://localhost:3000');
}
export function middleware(request: NextRequest) {
const origin = request.headers.get('origin') || '';
if (request.nextUrl.pathname.startsWith('/api/')) {
const isAllowedOrigin = allowedOrigins.includes(origin);
const response = NextResponse.next();
if (isAllowedOrigin) {
// 设置具体的域名(而非通配符)
response.headers.set('Access-Control-Allow-Origin', origin);
// 其他CORS头...
} else {
// 对于不在允许列表中的域名,仍然返回响应但不设置CORS头
// 这会导致浏览器阻止这些域的请求
}
return response;
}
return NextResponse.next();
}
3. 设置全局CORS头部
如果你想在全局范围内设置CORS头,可以使用next.config.js的headers选项:
// next.config.js
module.exports = {
async headers() {
return [
{
// 匹配所有API路由
source: "/api/:path*",
headers: [
{ key: "Access-Control-Allow-Credentials", value: "true" },
{ key: "Access-Control-Allow-Origin", value: "*" }, // 或者特定域名
{ key: "Access-Control-Allow-Methods", value: "GET,DELETE,PATCH,POST,PUT,OPTIONS" },
{ key: "Access-Control-Allow-Headers", value: "Content-Type, Authorization" }
]
}
]
}
};
这种方法适用于Pages Router和App Router,但请注意,它无法动态响应不同的Origin头。
从外部服务获取数据的CORS处理
当Next.js服务器需要请求外部API时,记住服务器到服务器的通信不受CORS限制。你可以使用以下模式来避免前端CORS问题:
使用API Routes或Route Handlers作为代理
// app/api/external-data/route.ts
import { NextResponse } from 'next/server';
export async function GET() {
// 服务器端请求不受CORS限制
const response = await fetch('https://external-api.com/data', {
headers: {
// 可以安全地添加API密钥,因为这个代码在服务器上运行
'Authorization': `Bearer ${process.env.API_KEY}`
}
});
const data = await response.json();
// 向客户端返回数据,无需CORS问题
return NextResponse.json(data);
}
使用Server Components获取数据
// app/dashboard/page.tsx - 服务器组件
import { fetchDashboardData } from '@/lib/api';
export default async function DashboardPage() {
// 在服务器组件中获取数据不会有CORS问题
const data = await fetchDashboardData();
return (
<div>
<h1>Dashboard</h1>
{/* 使用数据渲染UI */}
</div>
);
}
故障排除
如果你遇到CORS问题,检查以下几点:
- 确保添加了OPTIONS方法处理: 跨域的复杂请求需要正确响应预检请求
- 检查Origin匹配: 确保指定的
Access-Control-Allow-Origin与请求头中的Origin完全匹配(包括协议、子域名和端口) - 凭证请求特殊要求: 使用
credentials: 'include'时,Access-Control-Allow-Origin不能是* - Headers值验证: 确保
Access-Control-Allow-Headers包含请求中使用的所有自定义头 - 方法验证: 确保
Access-Control-Allow-Methods包含使用的HTTP方法 - 缓存设置: 使用
Access-Control-Max-Age减少预检请求频率 - 中间件与路由冲突: 避免在中间件和路由处理器中设置冲突的CORS头