Nginx无法识别React应用路由,请求配置指导
问题场景
基于create-react-app创建的React应用,使用react-router-dom做路由管理。通过Dockerfile.dev构建运行容器时,访问/mail路径能正常显示消息表格(数据来自/data/Messages.ts和/data/Config.ts)。但用Nginx部署时(通过Dockerfile.prod构建镜像,命令docker build -f Dockerfile.prod -t ui-nginx .,运行命令docker run -p 8080:80 ui-nginx),访问/mail仅显示空表格,且Nginx日志中没有/mail的请求记录。已尝试修改package.json的basename、在Dockerfile中设置ENV PUBLIC_URL=http://localhost:8080/、调整nginx.conf配置等方案,均未解决,需指导配置Nginx支持/mail路由导航。
问题分析与解决方案
核心定位
Nginx日志无/mail请求,说明访问/mail时Nginx已通过try_files规则将请求转发至index.html,前端路由已接管请求。空表格问题并非Nginx路由配置错误,而是生产构建后的数据加载逻辑异常。
排查与修复步骤
确认静态数据是否被打包
create-react-app生产构建仅打包public目录静态资源和src中被代码引用的文件。检查/data/Messages.ts和/data/Config.ts是否在MailView等组件中被正确导入,确保它们被包含在构建产物内:// 示例:确保导入路径正确 import { messages } from '../../data/Messages'; import { user } from '../../data/Config';验证生产构建产物
进入运行的Nginx容器,检查主JS文件是否包含数据内容:docker exec -it <容器ID> /bin/sh cat /usr/share/nginx/html/static/js/main.*.js | grep "messages"- 若能搜索到数据:问题出在前端组件渲染逻辑,检查浏览器控制台是否有JS报错。
- 若搜索不到:数据文件未被正确引入,需修正导入逻辑。
修正无效配置
- 无需设置
PUBLIC_URL=http://localhost:8080/,应用部署在根路径下,默认配置即可。 - 无需修改
package.json的basename,仅当应用部署在子路径时才需要该配置。
- 无需设置
确认Nginx配置有效性
当前nginx.conf已正确支持React浏览器路由,无需修改:server { listen 80; location / { root /usr/share/nginx/html; index index.html index.htm; try_files $uri $uri/ /index.html; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }
附:相关配置与日志
Docker日志
% docker run -p 8080:80 ui-nginx /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/ /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf 10-listen-on-ipv6-by-default.sh: info: /etc/nginx/conf.d/default.conf differs from the packaged version /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh /docker-entrypoint.sh: Configuration complete; ready for start up 2024/02/24 19:35:53 [notice] 1#1: using the "epoll" event method 2024/02/24 19:35:53 [notice] 1#1: nginx/1.24.0 2024/02/24 19:35:53 [notice] 1#1: built by gcc 12.2.1 20220924 (Alpine 12.2.1_git20220924-r4) 2024/02/24 19:35:53 [notice] 1#1: OS: Linux 6.6.12-linuxkit 2024/02/24 19:35:53 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576 2024/02/24 19:35:53 [notice] 1#1: start worker processes 2024/02/24 19:35:53 [notice] 1#1: start worker process 29 2024/02/24 19:35:53 [notice] 1#1: start worker process 30 2024/02/24 19:35:53 [notice] 1#1: start worker process 31 2024/02/24 19:35:53 [notice] 1#1: start worker process 32 2024/02/24 19:35:53 [notice] 1#1: start worker process 33 2024/02/24 19:35:53 [notice] 1#1: start worker process 34 2024/02/24 19:35:53 [notice] 1#1: start worker process 35 2024/02/24 19:35:53 [notice] 1#1: start worker process 36 192.168.65.1 - - [24/Feb/2024:19:36:29 +0000] "GET / HTTP/1.1" 200 887 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36" "-" 192.168.65.1 - - [24/Feb/2024:19:36:29 +0000] "GET /static/js/main.391febc6.js HTTP/1.1" 200 223526 "http://localhost:8080/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36" "-" 192.168.65.1 - - [24/Feb/2024:19:36:29 +0000] "GET /static/css/main.cb0cc79e.css HTTP/1.1" 200 4141 "http://localhost:8080/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36" "-" 192.168.65.1 - - [24/Feb/2024:19:36:29 +0000] "GET /manifest.json HTTP/1.1" 200 306 "http://localhost:8080/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36" "-"
项目结构
Dockerfile.prod README.md docker-compose.prod.yml nginx └── nginx.conf node_modules package-lock.json package.json public ├── delete.png ├── favicon.ico ├── index.html ├── manifest.json └── robots.txt src ├── App.css ├── App.test.tsx ├── App.tsx ├── components │ ├── Box.tsx │ ├── Button.tsx │ ├── Checkbox.tsx │ ├── FlexBox.tsx │ ├── Footer.tsx │ ├── Header.tsx │ ├── Home.tsx │ ├── Icons │ │ ├── CellControl.tsx │ │ └── Delete.tsx │ ├── Link.tsx │ ├── Mail │ │ ├── Mail.tsx │ │ ├── MailReply.tsx │ │ ├── MailSent.tsx │ │ ├── MailView.tsx │ │ ├── MessageNew.tsx │ │ ├── MessageReadMetaHeader.tsx │ │ ├── MessageView.tsx │ │ └── Table │ │ ├── Table.tsx │ │ ├── TableHeaders.tsx │ │ └── TableRows.tsx │ ├── NotFound.tsx │ ├── Select.tsx │ ├── TextArea.tsx │ ├── TextAreaImageAdd.tsx │ ├── TextField.tsx │ ├── TextFieldImageAdd.tsx │ └── Typography.tsx ├── data │ ├── Config.ts │ ├── Constants.ts │ ├── Messages.ts ├── index.css ├── index.tsx ├── react-app-env.d.ts ├── reportWebVitals.ts ├── setupTests.ts ├── tests │ ├── Button.test.tsx │ ├── Header.test.tsx │ ├── Home.test.tsx │ ├── Mail.test.tsx │ └── Select.test.tsx ├── types │ └── types.tsx └── utils └── utils.tsx tsconfig.json
nginx.conf
server { listen 80; location / { root /usr/share/nginx/html; index index.html index.htm; try_files $uri $uri/ /index.html; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }
Dockerfile.prod
FROM node:21-alpine as build WORKDIR /app ENV PATH /app/node_modules/.bin:$PATH COPY public /app/public COPY package.json package-lock.json /app/ RUN npm ci COPY . ./ RUN npm run build FROM nginx:stable-alpine COPY --from=build /app/build /usr/share/nginx/html COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]
App.tsx
import './App.css'; import { Route, Routes } from 'react-router-dom'; import { StringConstants } from "./data/Constants"; import { Footer } from './components/Footer'; import { Header } from './components/Header'; import { Home } from './components/Home'; import { MailReply } from "./components/Mail/MailReply"; import { MailSent } from "./components/Mail/MailSent"; import { MailView } from "./components/Mail/MailView"; import { useState } from "react"; import { user } from "./data/Config"; import NotFound from "./components/NotFound"; function App() { const [mailFactor, setMailFactor] = useState<number>(-1) const [userLoggedIn] = useState<boolean>(false) return ( <div className="App"> <Header setMailFactor={setMailFactor} userLoggedIn={userLoggedIn} /> <Routes> <Route path="/" element={ <Home /> } /> <Route path="mail" > <Route index element={<MailView title={StringConstants.INBOX} mailFactor={mailFactor} setMailFactor={setMailFactor} />} /> <Route path="sent" element={<MailView title={StringConstants.SENT} mailFactor={mailFactor} setMailFactor={setMailFactor} />} /> <Route path="deleted" element={<MailView title={StringConstants.DELETED} mailFactor={mailFactor} setMailFactor={setMailFactor} />} /> <Route path="new" element={<MailView title={StringConstants.BUTTON_TEXT_NEW} mailFactor={mailFactor} setMailFactor={setMailFactor} />} /> <Route path="send" element={<MailSent />} /> <Route path="reply" element={<MailReply />} /> </Route> <Route path='*' element={<NotFound />}/> </Routes> <Footer /> </div> ); } export default App;
Dockerfile.dev
FROM node:20-alpine as build WORKDIR /app COPY public /app/public COPY package.json package-lock.json /app/ ENV PATH /app/node_modules/.bin:$PATH RUN npm ci COPY . ./ CMD ["npm", "start"]
内容的提问来源于stack exchange,提问作者chocalaca

