Docker部署React时Apollo useQuery无限循环请求问题
问题根因
死循环是代码逻辑缺陷+部署配置问题共同导致的,本地非Docker环境未触发是因为环境配置差异掩盖了代码问题:
- 核心代码缺陷1:状态更新逻辑形成闭环触发点
现有认证流程存在逻辑漏洞:- 初始状态下
isInitialized=false→performAuthentication=true→skip=false→ useQuery发起请求,loading=true - 当请求返回(无论成功/失败),
loading=false触发useEffect执行redux状态更新:成功时将isInitialized设为true,失败时调用resetUser() - 如果
resetUser的reducer逻辑将isInitialized重置为false(而非标记为初始化完成),会直接导致performAuthentication变回true →skip=false→ 重新发起请求,形成无限循环。 - 现有useEffect仅依赖
loading字段,未将data/error加入依赖数组,当Apollo Client因skip状态切换清空历史结果时,会出现状态不一致导致重复触发。
- 初始状态下
- 核心代码缺陷2:渲染阶段直接执行副作用
AuthProvider组件在渲染主流程中(未包裹在useEffect/事件回调内)直接调用dispatch,即使外层包了开发环境判断,一旦环境变量匹配错误,就会触发「渲染→dispatch更新状态→重渲染→再次dispatch」的死循环,该写法本身也违反React副作用执行规则。 - 部署配置问题:Docker环境下接口响应格式异常
服务端可收到请求但前端持续重发,说明Docker环境下前端拿到的不是合法GraphQL响应:- 现有Dockerfile仅执行了前端构建,未配置静态资源服务的路由规则:如果用express/nginx托管构建产物,没有将
/graphql路径的请求反向代理到Django后端,接口请求会返回单页应用的index.html内容,Apollo解析失败直接抛错 - 生产构建时未正确注入环境变量,比如Apollo Client请求地址配置错误、跨域头未正确配置,导致请求虽到达服务端,但前端拿到的响应被拦截/格式错误
- 现有Dockerfile仅执行了前端构建,未配置静态资源服务的路由规则:如果用express/nginx托管构建产物,没有将
- 本地环境未触发的原因
本地开发时npm start走webpack devServer,内置代理会正确转发/graphql请求到后端;如果本地开启了REACT_APP_REMOTE_SERVER=false的mock配置,甚至不会发起真实请求,自然不会触发循环。
修复方案
按优先级依次修复:
1. 修正AuthProvider代码逻辑,从根源切断循环可能
const AuthContext = createContext(null); function AuthProvider({ children }) { const authenticationStore = useSelector((state) => state.authentication); const dispatch = useDispatch(); const navigate = useNavigate(); // 缓存计算值,避免不必要重渲染 const performAuthentication = useMemo(() => !authenticationStore.isInitialized, [authenticationStore.isInitialized]); const isMockAuth = process.env.NODE_ENV === "development" && process.env.REACT_APP_REMOTE_SERVER === "false"; const { loading, error, data } = useQuery( gql` query { # 填写你的认证查询内容 } `, { skip: !performAuthentication || isMockAuth, errorPolicy: 'all', fetchPolicy: 'network-only' } ); // 所有副作用统一放到useEffect中执行,补全依赖数组 useEffect(() => { // 开发环境mock逻辑移入effect,禁止渲染阶段直接dispatch if (isMockAuth && performAuthentication) { dispatch( setUser({ isInitialized: true, isAuthenticated: true, user: { id: 1, name: "John Doe", firstName: "John", lastName: "Doe", email: "john@hrm.com", }, }) ); return; } if (loading) return; if (data?.myProfile) { dispatch( setUser({ isInitialized: true, isAuthenticated: true, user: data.myProfile, }) ); } else { // 关键修正:无论认证成功/失败,只要完成一次请求就标记isInitialized为true,禁止重置为false // 认证失败仅需将isAuthenticated设为false,否则会触发无限重发 dispatch( setUser({ isInitialized: true, isAuthenticated: false, user: null }) ); } }, [loading, data, error, isMockAuth, performAuthentication, dispatch]); // 加载状态判断 if (performAuthentication && !isMockAuth && loading) { return <div>加载中...</div>; } return <AuthContext.Provider value={/* 填写你的上下文传值 */}>{children}</AuthContext.Provider>; }
关键修正点:
- 所有dispatch逻辑全部移入useEffect,杜绝渲染阶段直接触发状态更新
- 补全useEffect依赖数组,避免闭包陷阱
- 修正状态更新逻辑:无论认证结果如何,完成一次请求后即标记初始化完成,从根源切断重复请求的触发条件
- 用useMemo缓存计算值,减少不必要的重渲染
2. 修正Docker部署配置
- 如果用express托管构建产物,先新增服务启动文件配置路由和反向代理:
- 项目根目录新建
server.js:
const express = require('express'); const { createProxyMiddleware } = require('http-proxy-middleware'); const path = require('path'); const app = express(); // 将/graphql请求反向代理到Django服务,替换为实际的Django服务地址 app.use('/graphql', createProxyMiddleware({ target: 'http://your-django-service:8000', changeOrigin: true })); // 托管静态构建产物 app.use(express.static(path.join(__dirname, 'build'))); // 适配前端路由,所有非接口请求返回index.html app.get('*', (req, res) => { res.sendFile(path.join(__dirname, 'build', 'index.html')); }); app.listen(3000, () => { console.log('Frontend service running on port 3000'); });- 修改Dockerfile,补全依赖、环境变量注入和启动命令:
FROM node:14.19.1 WORKDIR /app COPY package.json ./ RUN npm install express http-proxy-middleware COPY . ./ # 构建时传入需要的前端环境变量 ARG REACT_APP_GRAPHQL_URI ENV REACT_APP_GRAPHQL_URI=$REACT_APP_GRAPHQL_URI RUN npm run build EXPOSE 3000 CMD ["node", "server.js"] - 项目根目录新建
- 如果用nginx托管,需要在nginx配置中增加
/graphql路径的反向代理规则,确保接口请求正确转发到Django服务,不会被前端路由规则拦截返回index.html - 构建镜像时确保所有
REACT_APP_开头的环境变量正确传入,CRA执行npm run build时会自动将NODE_ENV设为production,无需手动配置
3. 验证步骤
- 本地先执行
npm run build,用静态服务托管构建产物,验证生产包的认证流程正常,无循环请求 - 本地验证通过后再构建Docker镜像,启动后先访问
/graphql路径,确认返回GraphQL接口响应而非index.html内容 - 观察网络面板,确认认证查询仅发起一次,完成后可正常跳转页面
内容的提问来源于stack exchange,提问作者Sina Farahani
相关产品推荐
相关产品推荐

