Next.js 跨域问题的各种解法

Next.js 跨域问题的各种解法

Author: huyikai

nextjsreactfrontend

调用跨域接口

使用服务端组件

第一种解决方案是改为使用服务端组件。

跨域错误是浏览器的行为,改为服务端组件,本质是改成 Node 后端调用,自然不会出现跨域问题。

修改 app/page.js,代码如下:

export default async function Home() {
const response = await fetch('https://www.juejin.cn');
const html = await response.text();
return <div>{html}</div>;
}

使用后端接口转发

如果不能改为使用服务端组件,我们也可以使用 Next.js 自定义一个接口,前端改为调用此接口。

新建 app/api/juejin/route.js,代码如下:

export async function GET() {
const res = await fetch('https://www.juejin.cn');
const data = await res.text();
return Response.json({ data });
}

这样我们就实现了一个 http://localhost:3000/api/juejin 的 GET 接口。

修改 app/page.js,依然是使用客户端组件,改为调用此接口。代码如下:

'use client';
import { useEffect, useState } from 'react';
export default function Home() {
const [html, setHTML] = useState([]);
useEffect(() => {
const fetchArticleList = async () => {
const response = await fetch('http://localhost:3000/api/juejin');
const data = await response.json();
setHTML(data.data);
};
fetchArticleList();
}, []);
return <div>{html}</div>;
}

使用服务端组件的时候,因为改为后端调用,所以浏览器中并不会查看到该请求。而使用这种方法,本质还是在浏览器端发送请求,所以可以在浏览器中查看到请求。

使用 rewrites 配置项

Next.js 其实提供了 rewrites 配置项用于重写请求。这算是解决跨域问题常用的一种方式。

重写会将传入的请求路径映射到其他目标路径。你可以把它理解为代理,并且它会屏蔽目标路径,使得用户看起来并没有改变其在网站上的位置。

修改 next.config.mjs,代码如下:

/** @type {import('next').NextConfig} */
const nextConfig = {
async rewrites() {
return [
{
source: '/api/juejin',
destination: 'https://juejin.cn'
}
];
}
};
export default nextConfig;

通过配置 next.config.js 中的 rewrites 配置项,当请求 /api/juejin 的时候,代理请求 https://juejin.cn

修改 app/page.js,代码如下:

'use client';
import { useEffect, useState } from 'react';
export default function Home() {
const [html, setHTML] = useState([]);
useEffect(() => {
const fetchArticleList = async () => {
const response = await fetch('http://localhost:3000/api/juejin');
const data = await response.text();
setHTML(data);
};
fetchArticleList();
}, []);
return <div>{html}</div>;
}

使用中间件

不止 next.config.js 可以配置重写,你也可以在中间件中实现重写。

根目录新建 middleware.js,代码如下:

import { NextResponse } from 'next/server';
export function middleware(request) {
if (request.nextUrl.pathname.startsWith('/api/juejin')) {
return NextResponse.rewrite(new URL('https://juejin.cn'));
}
}
export const config = {
matcher: '/api/:path*'
};

修改 app/page.js,代码如下:

'use client';
import { useEffect, useState } from 'react';
export default function Home() {
const [html, setHTML] = useState([]);
useEffect(() => {
const fetchArticleList = async () => {
const response = await fetch('http://localhost:3000/api/juejin');
const data = await response.text();
setHTML(data);
};
fetchArticleList();
}, []);
return <div>{html}</div>;
}

实现跨域接口

实现的关键在于添加 Access-Control-Allow-Origin 响应头

路由处理程序

如果只有一个或者少量接口需要实现跨域,那可以直接写在对应的路由处理程序中。

新建 app/api/blog/route.js,代码如下:

import { NextResponse } from 'next/server';
export async function GET() {
const data = { success: true, data: { name: 'yayu' } };
return NextResponse.json(data);
}

这样我们就实现了一个接口,它的地址是 http://localhost:3000/api/blog

很明显会出现 CORS 错误。

修改 app/api/blog/route.js,代码如下:

import { NextResponse } from 'next/server';
export async function GET() {
const data = { success: true, data: { name: 'yayu' } };
return NextResponse.json(data, {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
}
});
}

此时接口就已经设置了 CORS 支持了跨域请求。

中间件设置

如果是多个接口,则可以在中间件或者 next.config.js 中配置。

我们先说使用中间件,这算是解决跨域问题最常用的解决方法。

app/api/blog/route.js 代码修改回之前有跨域问题的代码:

import { NextResponse } from 'next/server';
export async function GET() {
const data = { success: true, data: { name: 'yayu' } };
return NextResponse.json(data);
}

修改 middleware.js,代码如下:

import { NextResponse } from 'next/server';
const allowedOrigins = ['https://juejin.cn'];
const corsOptions = {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
};
export function middleware(request) {
// 检查请求的 origin 属性
const origin = request.headers.get('origin') ?? '';
const isAllowedOrigin = allowedOrigins.includes(origin);
// 处理预检 OPTIONS 请求
const isPreflight = request.method === 'OPTIONS';
if (isPreflight) {
const preflightHeaders = {
...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
...corsOptions
};
return NextResponse.json({}, { headers: preflightHeaders });
}
// 处理普通请求
const response = NextResponse.next();
if (isAllowedOrigin) {
response.headers.set('Access-Control-Allow-Origin', origin);
}
Object.entries(corsOptions).forEach(([key, value]) => {
response.headers.set(key, value);
});
return response;
}
export const config = {
matcher: '/api/:path*'
};

中间件为传入的请求提供了全局控制机制。我们在 middleware 中为 /api/xxx统一进行了 CORS 的判断和处理

注:注意我们代码中的 Access-Control-Allow-Headers,如果我们携带了其他请求头,因为请求头不匹配,也会导致 CORS 错误。

使用 headers 配置项

除了在 middleware 中设置,还可以借助 next.config.js 的 headers 配置项。

修改 next.config.mjs,代码如下:

/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
source: '/api/:path*',
headers: [
{
key: 'Access-Control-Allow-Origin',
value: '*'
},
{
key: 'Access-Control-Allow-Methods',
value: 'GET, POST, PUT, DELETE, OPTIONS'
},
{
key: 'Access-Control-Allow-Headers',
value: 'Content-Type, Authorization'
}
]
}
];
}
};
export default nextConfig;

这算是一种简单的实现方式。如果你需要更高的灵活度,则还是采用中间件的形式。此时也能够正常请求

Vercel 配置项

{
"headers": [
{
"source": "/api/(.*)",
"headers": [
{ "key": "Access-Control-Allow-Credentials", "value": "true" },
{ "key": "Access-Control-Allow-Origin", "value": "*" },
{
"key": "Access-Control-Allow-Methods",
"value": "GET,OPTIONS,PATCH,DELETE,POST,PUT"
},
{
"key": "Access-Control-Allow-Headers",
"value": "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version"
}
]
}
]
}