You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

部署MERN栈至Vercel遇CORS错误,登录后跳转异常

问题:MERN栈部署Vercel后登录跳转异常及CORS错误

我将MERN栈的前后端代码部署至Vercel,注册时前端可正常将邮箱等信息发送至后端并存入MongoDB Atlas在线数据库。但使用注册凭证登录时,验证通过后页面刚加载到下一页就跳回登录页,控制台出现如下CORS错误:

Access to fetch at 'https://swap-ease-now-backend.vercel.app/details' from origin 'https://swap-ease-now.vercel.app' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
Details.js:9     GET https://swap-ease-now-backend.vercel.app/details net::ERR_FAILED
(anonymous) @ Details.js:9
f @ regeneratorRuntime.js:44
(anonymous) @ regeneratorRuntime.js:125
(anonymous) @ regeneratorRuntime.js:69
Be @ asyncToGenerator.js:3
o @ asyncToGenerator.js:22
(anonymous) @ asyncToGenerator.js:27
(anonymous) @ asyncToGenerator.js:19
(anonymous) @ Details.js:7
(anonymous) @ Details.js:32
as @ react-dom.production.min.js:244
ku @ react-dom.production.min.js:286
(anonymous) @ react-dom.production.min.js:282
w @ scheduler.production.min.js:13
P @ scheduler.production.min.js:14
Details.js:27 TypeError: Failed to fetch
    at Details.js:9:25
    at f (regeneratorRuntime.js:44:17)
    at Generator.<anonymous> (regeneratorRuntime.js:125:22)
    at Generator.next (regeneratorRuntime.js:69:21)
    at Be (asyncToGenerator.js:3:20)
    at o (asyncToGenerator.js:22:9)
    at asyncToGenerator.js:27:7
    at new Promise (<anonymous>)
    at asyncToGenerator.js:19:12
    at Details.js:7:20

我不确定错误源于Cookie、Token还是CORS,因为若为CORS问题,注册时的前端数据也无法提交至后端。以下是后端的代码:

app.js

const dotenv=require("dotenv");
const mongoose=require('mongoose');
const express=require('express');
const app=express();
const cors = require('cors');


dotenv.config({path: './config.env'});

const User=require('./models/userSchema');

require('./db/conn.js');
app.use(express.json());
app.use(cors());
// to link the router file to route the path easily
app.use(require('./router/auth'));

const PORT=process.env.PORT || 5000;
app.get('/signup',(req,res)=>
{
    res.send('Hello at signup');
})

app.listen(PORT,()=>
{
    console.log(`running at port number ${PORT}`);
})

auth.js(路由文件)

const jwt=require("jsonwebtoken");
const express=require('express');
const router=express.Router();
const bcrypt=require('bcryptjs');
const authenticate=require("../middleware/authenticate");
const fs = require("fs");
const path = require("path");
require('../db/conn');

const User=require('../models/userSchema');

const cookieParser = require("cookie-parser");
const cors = require("cors");
const Authenticate = require("../middleware/authenticate");
router.use(cookieParser());
router.use(cors());

//login route
router.post('/signin',async (req,res)=>
{
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Methods', 'POST');
    res.header('Access-Control-Allow-Headers', 'Content-Type');
    try
    {
        let token;
        const {email,password}=req.body;
        if(!email || !password)
        {
            return res.status(400).json({error:"Either one or more fields are empty...."});
        }
        const userLogin=await User.findOne({email:email});
        if(userLogin)
        {
            const isMatch=await bcrypt.compare(password,userLogin.password);
            token=await userLogin.generateAuthToken();
            console.log(token);

            res.cookie("jwtoken",token,{
            expires: new Date(Date.now()+ 25892000000),
            httpOnly:true
            });
            if(!isMatch)
            {
                res.status(400).json({error:"Invalid user credentials...."});
            }
            else{
                res.json({message:"Successful"});
            }
        }
        else
        {
            res.status(400).json({error:"Invalid user credentials...."}); 
        }
        
    }
    catch(err){
        console.log(err);
    }

}
)

router.get('/details', authenticate,(req,res)=>
{
    res.send(req.rootUser);
})

问题根源

错误提示明确:当请求的credentials模式为include时,Access-Control-Allow-Origin不能使用通配符*。登录时后端设置了HttpOnly Cookie(jwtoken),前端请求/details接口时会自动携带该Cookie,此时请求的credentials模式为include,但后端CORS配置使用了通配符*,导致预请求失败,/details接口无法返回用户数据,前端鉴权逻辑因拿不到数据触发跳转,回到登录页。

注册时未触发此问题,是因为注册接口没有携带Cookie,请求的credentials模式为same-origin,通配符*可以正常工作。

修复步骤

  1. 全局配置CORS(替换app.js中的app.use(cors());)
    不要使用默认的通配符,指定允许的前端域名,同时开启凭证支持:
    app.use(cors({
      origin: process.env.FRONTEND_URL, // 例如"https://swap-ease-now.vercel.app",建议存在.env环境变量中
      credentials: true
    }));
    
  2. 清理auth.js中的冗余配置
    • 删除router.use(cors());,全局已配置CORS,无需重复设置
    • 删除signin接口中手动设置的res.header三行代码,cors中间件会自动处理跨域头
  3. 确保前端请求携带凭证
    前端调用/details的请求需开启凭证携带:
    • Fetch示例:
      fetch('https://swap-ease-now-backend.vercel.app/details', {
        credentials: 'include'
      })
      
    • Axios示例:
      axios.get('https://swap-ease-now-backend.vercel.app/details', {
        withCredentials: true
      })
      
  4. Vercel部署配置
    后端部署到Vercel时,需在项目环境变量中添加FRONTEND_URL,值为你的前端域名,确保CORS配置生效。

内容的提问来源于stack exchange,提问作者Aman Kakkar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 11:22:24