两个独立域名NextJS应用调用同一GraphQL后端的CORS问题求解
跨域问题解决方案:两个NextJS应用共享GraphQL后端
问题背景
我有两个分属不同代码仓库、使用不同域名的NextJS应用,希望第二个应用(应用B)调用第一个应用(应用A)的GraphQL+Apollo后端,但因域名不匹配遇到了Access-Control-Allow-Origin跨域问题。
架构说明
两个UI、两个域名、一个后端(架构示意图如下):
环境差异
- 本地环境运行正常:
- 应用A:
localhost:3000 - 应用B:
localhost:4000
- 应用A:
- 线上环境切换域名后出现CORS问题,应用B的
NEXT_PUBLIC_APOLLO_BASE_URL配置为应用A的线上域名。
Apollo客户端配置代码
import { ApolloClient, ApolloClientOptions, InMemoryCache, NormalizedCacheObject, createHttpLink, } from '@apollo/client' import { auth } from 'app/firebase/clientApp' import { setContext } from '@apollo/client/link/context' const apolloCache = new InMemoryCache({ typePolicies: { SearchConfigOption: { keyFields: ['pathName'], }, Query: { fields: { /* filterItems: { keyArgs: ['user'], merge(existing, incoming) { if (!incoming) return existing if (!existing) return incoming // existing will be empty the first time const { items, ...rest } = incoming let result = rest result.items = [...existing.items, ...items] // Merge existing items with the items from incoming return result }, },*/ }, }, }, }) const httpLink = createHttpLink({ uri: `${process.env.NEXT_PUBLIC_APOLLO_BASE_URL}api/graphql`, credentials: 'include', }) const authLink = setContext(async (_, { headers }) => { const user = auth.currentUser const token = user && (await user.getIdToken()) const modifiedHeader = { headers: { ...headers, authorization: token ? `Bearer ${token}` : 'Bearer null', }, } return modifiedHeader }) const client = new ApolloClient({ ssrMode: typeof window === 'undefined', link: authLink.concat(httpLink), cache: apolloCache, connectToDevTools: process.env.NODE_ENV !== 'production', credentials: 'include', }) export default client
最佳解决方案
1. 在应用A的NextJS后端配置CORS白名单
这是最直接的解决方式,在应用A的GraphQL API路由中添加CORS规则,明确允许应用B的域名访问。
实现步骤:
- 在应用A的
pages/api/graphql.ts(或对应路由文件)中,引入cors库并配置:import Cors from 'cors' import { ApolloServer } from '@apollo/server' import { startServerAndCreateNextHandler } from '@as-integrations/next' // 初始化CORS配置 const cors = Cors({ origin: process.env.NEXT_PUBLIC_APP_B_DOMAIN, // 应用B的线上域名,例如"https://app-b.example.com" credentials: true, // 匹配Apollo客户端的credentials: 'include'配置 }) // 包装Apollo请求处理逻辑,先执行CORS校验 const handler = startServerAndCreateNextHandler(server, { context: async (req, res) => { await new Promise((resolve, reject) => { cors(req, res, (result) => { if (result instanceof Error) return reject(result) resolve(result) }) }) return { req, res } }, }) export default handler - 注意:生产环境禁止使用
*作为origin(会和credentials: true冲突),如果需要允许多个域名,可传入数组:origin: ['https://app-b.example.com', 'https://app-c.example.com']。
2. 应用B侧配置NextJS重写代理请求
通过应用B的重写规则,将GraphQL请求代理到应用A的域名,浏览器端请求会指向应用B自身域名,规避跨域限制。
实现步骤:
- 在应用B的
next.config.js中添加重写规则:/** @type {import('next').NextConfig} */ const nextConfig = { async rewrites() { return [ { source: '/api/graphql', // 前端请求该路径 destination: `${process.env.NEXT_PUBLIC_APOLLO_BASE_URL}/api/graphql`, // 代理到应用A的接口 }, ] }, } module.exports = nextConfig - 修改应用B的Apollo客户端
uri为自身路径:const httpLink = createHttpLink({ uri: '/api/graphql', // 不再直接写应用A的域名 credentials: 'include', }) - 优势:无需修改应用A代码,跨域处理完全在应用B内部完成,适合无法改动应用A配置的场景。
3. 剥离独立GraphQL后端(长期最佳实践)
如果未来有更多应用需要调用该GraphQL服务,建议将后端从应用A中剥离,部署为独立服务(例如使用https://api.example.com域名),统一配置CORS允许所有需要的前端域名访问。
核心优势:
- 架构解耦,后端服务与前端应用独立部署、维护;
- 便于统一管理API权限、监控和版本迭代;
- 避免单个前端应用的部署影响后端服务稳定性。
4. 本地开发环境补充(可选)
若需模拟线上跨域场景,可在应用B的next.config.js中添加开发环境重写:
const nextConfig = { async rewrites() { if (process.env.NODE_ENV === 'development') { return [ { source: '/api/graphql', destination: 'http://localhost:3000/api/graphql', }, ] } return [] }, }
内容的提问来源于stack exchange,提问作者Mona Fang
相关产品推荐
相关产品推荐

