一日一技:Next.js如何正確處理跨域問題?
我以前一直使用Vue來寫前端。去年下半年接手了一個(gè)基于React + Next.js的項(xiàng)目,于是順帶學(xué)習(xí)了一下Next.js。由于Next.js的特點(diǎn),這個(gè)項(xiàng)目的前后端是放在一起的。一開始沒什么問題,看了半天文檔就上手了。
上周我們需要在另一個(gè)網(wǎng)頁項(xiàng)目中,調(diào)用這個(gè)項(xiàng)目的后端接口,于是就需要處理跨域請求的問題。但我發(fā)現(xiàn)按照網(wǎng)上的方法,跨域問題依然存在。這個(gè)問題浪費(fèi)了我不少時(shí)間,好在最后終于找到了原因。記錄在這里,免得大家跟我一樣踩坑。
為了復(fù)現(xiàn)這個(gè)問題,我們先來創(chuàng)建一個(gè)Next.js項(xiàng)目。執(zhí)行代碼創(chuàng)建代碼腳手架:
npx create-next-app test_cors
使用TypeScript,其他選項(xiàng)選擇默認(rèn),如下圖所示:
圖片
命令執(zhí)行完成以后,會生成一個(gè)test_cors文件夾,在文件夾中創(chuàng)建文件pages/api/test.ts。內(nèi)容如下:
import { NextResponse } from 'next/server'
export const config = {
runtime: "edge"
}
export interface UserInfo {
name: string
age: number
address: string
}
const handler = async (req: Request): Promise<Response> => {
const user = (await req.json()) as UserInfo
return NextResponse.json({success: true,
msg: `你的名字是${user.name}, 今年${user.age}歲`})
}
export default handler;
如下圖所示:
圖片
然后運(yùn)行命令npm run dev。這個(gè)后端接口就啟動起來了。我們可以使用Postman來進(jìn)行測試:
圖片
接下來,我們來寫一段HTML代碼,觸發(fā)跨域問題:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>API 請求示例</title>
<script>
// 當(dāng)按鈕被點(diǎn)擊時(shí)執(zhí)行此函數(shù)
function sendRequest() {
// 創(chuàng)建一個(gè)新的 XMLHttpRequest 對象
var xhr = new XMLHttpRequest();
// 配置請求類型、URL 以及異步處理
xhr.open('POST', 'http://127.0.0.1:3000/api/test', true);
// 設(shè)置請求頭
xhr.setRequestHeader('Content-Type', 'application/json');
// ... 其他請求頭設(shè)置
// 設(shè)置響應(yīng)類型
xhr.responseType = 'json';
// 定義請求完成的回調(diào)函數(shù)
xhr.onload = function () {
if (xhr.status === 200) {
// 請求成功,處理響應(yīng)數(shù)據(jù)
document.getElementById('response').innerText = JSON.stringify(xhr.response);
} else {
// 請求失敗,處理錯(cuò)誤
document.getElementById('response').innerText = '請求失敗: ' + xhr.status;
}
};
// 發(fā)送請求
xhr.send(JSON.stringify({name: "青南", age: 20, "address": "上海"}));
}
</script>
</head>
<body>
<button onclick="sendRequest()">發(fā)送請求</button>
<div id="response"></div>
</body>
</html>
直接雙擊打開這個(gè)html文件,點(diǎn)擊頁面上的按扭,就會觸發(fā)跨域報(bào)錯(cuò),如下圖所示:
圖片
然后,你在網(wǎng)上用關(guān)鍵詞搜索next.js 跨域或者next.js cors,一般看到的文章都會讓你直接在next.config.js文件中添加響應(yīng)頭,如下圖所示:
圖片
你按照這些文章中寫到方法加了配置,重啟服務(wù),然后用Postman來測試,你會發(fā)現(xiàn)返回的響應(yīng)頭里面確實(shí)已經(jīng)有這幾項(xiàng)了,如下圖所示:
圖片
但當(dāng)你使用HTML頁面來測試時(shí),跨域的報(bào)錯(cuò)還在。
你連續(xù)打開Google上面10篇講Next.js跨域的文章,無論是中文博客還是英文博客,甚至你直接使用ChatGPT來問,他們給你的回復(fù)肯定都是上面的這個(gè)方法。但是無論你怎么測試,跨域問題還在。
實(shí)際上,跨域就是這樣配置的。你的配置沒有任何問題。問題出現(xiàn)在你的后端代碼上,如下圖所示:
圖片
首先你需要是一個(gè)POST請求,你才能執(zhí)行await req.json()。而瀏覽器在判斷能不能跨域時(shí),會首先發(fā)送一個(gè)OPTIONS請求,如下圖所示:
圖片
這個(gè)請求也會走到你的這段后端代碼里面。但由于OPTIONS請求沒有Body,于是代碼運(yùn)行到await req.json()時(shí),就會報(bào)錯(cuò)。于是瀏覽器認(rèn)為OPTIONS請求沒有返回status 200,因此強(qiáng)行認(rèn)為你的接口不支持跨域。
那么解決方法也非常簡單,提前判斷一下請求方法是不是OPTIONS就可以了:
if(req.method === 'OPTIONS') {
return NextResponse.next()
}
如下圖所示:
圖片
運(yùn)行效果如下圖所示,跨域成功:
圖片
這個(gè)問題對于資深前端來說,可能不值一提。但對于后端兼職前端的人,或者第一次接觸Next.js的人來說,可能是一個(gè)深坑,會浪費(fèi)很多的時(shí)間。