Next.js 跨域问题的各种解法
前言
跨域是使用 Next.js 常遇到的问题,问题主要分为两类:
- 如何调用一个跨域接口?
- 如何实现一个跨域接口?
本篇我们会先从跨域的基础知识开始讲起,然后讲解 Next.js 下这两大类问题的各种解决方案,帮你系统解决跨域问题!快收藏、点赞这篇文章留做备忘吧!
1. 基础知识
我们先复习一下跨域与 CORS 的基础知识。
1.1. 跨域
跨域是浏览器的行为。 出于安全性,浏览器会限制脚本内发起的跨源 HTTP 请求。XMLHttpRequest 和 Fetch API 都遵循同源策略。
所谓同源策略指的是两个 URL 的协议/主机名/端口一致。 例如,https://www.taobao.com
,它的协议是 https
,主机名是 www.taobao.com
,端口是 443
。协议、主机名、端口三者完全一致才算是同源。
这意味着使用这些 API 的 Web 应用程序只能从加载应用程序的同一个域请求 HTTP 资源,除非响应报文包含了正确 CORS 响应头。
1.2. CORS
为了能够跨域请求,便有了 CORS(Cross-Origin Resource Sharing,跨源资源共享),它是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其他源(域、协议或端口),使得浏览器允许这些源访问加载自己的资源。
简单的来说,CORS 也是浏览器实现的机制,它允许跨域请求 HTTP 资源。
浏览器会判断发送的请求是否跨域,如果跨域,请求头会带上 Origin 属性,数据返回后,检查其响应头中的 Access-Control-Allow-Origin 是否匹配,如果不匹配,则报 CORS 错误。
但因为设置了 CORS,所以可以正常请求。我们查看下请求头和响应头:
当然这是针对简单的请求,对于复杂的请求, 比如携带自定义请求头的 POST,浏览器则会发送用于预检的 OPTIONS 请求。
简单来说就是,浏览器在发送正式的请求之前也发送一个用于检查是否可以跨域请求的 OPTIONS 类型的请求。如果请求通过,再正式发送请求。
比如我们在控制台发送一个携带自定义请求头的 POST 请求:
效果如下:
此时浏览器会先发送一个 OPTIONS 请求,它的请求头中会携带:
服务器返回响应头:
意思就是:行,都接受了!接着浏览器就会发送实际请求。
关于跨域和 CORS 的基础知识就这些,那我们该如何 Next.js 在处理跨域呢?
我们先说在 Next.js 中如何调用跨域接口。
2. 调用一个跨域接口?
我们先模拟一个跨域问题。使用官方脚手架,创建一个 Next.js 项目:
npx create-next-app@latest
修改 app/page.js
,代码如下:
那么该如何避免出现 CORS 错误呢?
2.1. 使用服务端组件
第一种解决方案是改为使用服务端组件。
跨域错误是浏览器的行为,改为服务端组件,本质是改成 Node 后端调用,自然不会出现跨域问题。
修改 app/page.js
,代码如下:
浏览器效果如下:
2.2. 使用后端接口转发
如果不能改为使用服务端组件,我们也可以使用 Next.js 自定义一个接口,前端改为调用此接口。
修改 app/page.js
,依然是使用客户端组件,改为调用此接口。代码如下:
浏览器效果如下:
使用服务端组件的时候,因为改为后端调用,所以浏览器中并不会查看到该请求。而使用这种方法,本质还是在浏览器端发送请求,所以可以在浏览器中查看到请求。
2.3. 使用 rewrites 配置项
Next.js 其实提供了 rewrites 配置项用于重写请求。这算是解决跨域问题常用的一种方式。
重写会将传入的请求路径映射到其他目标路径。你可以把它理解为代理,并且它会屏蔽目标路径,使得用户看起来并没有改变其在网站上的位置。
修改 next.config.mjs
,代码如下:
修改 app/page.js
,代码如下:
浏览器效果如下:
2.4. 使用中间件
不止 next.config.js
可以配置重写,你也可以在中间件中实现重写。
根目录新建 middleware.js
,代码如下:
修改 app/page.js
,代码如下:
此时浏览器效果不变:
3. 实现一个跨域接口?
如果我们要实现一个接口,允许其他源访问呢?
实现的关键在于添加 Access-Control-Allow-Origin 响应头,那么在哪里添加这个响应头呢?
其实有很多种方案可以选择:
3.1. 路由处理程序
如果只有一个或者少量接口需要实现跨域,那可以直接写在对应的路由处理程序中。
新建 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',
},
})
}
3.2. 中间件设置
如果是多个接口,则可以在中间件或者 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
,代码如下:
我们再试试预检请求,浏览器控制台运行:
fetch("http://localhost:3000/api/blog", { method: "POST", headers: {
"Content-Type": "application/xml"
}});
因为自定义了请求头的值,此时会触发用于预检的 OPTIONS 请求。也能够正常请求:
注:注意我们代码中的 Access-Control-Allow-Headers,如果我们携带了其他请求头,因为请求头不匹配,也会导致 CORS 错误。
3.3. 使用 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;
这算是一种简单的实现方式。如果你需要更高的灵活度,则还是采用中间件的形式。此时也能够正常请求:
3.4. Vercel 配置项
如果你用 Vercel,你也可以在 vercel.json 中进行配置:
{
"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" }
]
}
]
}
参考链接
转载自:https://juejin.cn/post/7366177423775531008