create-react-app 部署实战:从 build 产物到客户端路由回退与多平台发布

发布时间:2026/9/5 16:04:44
create-react-app 部署实战:从 build 产物到客户端路由回退与多平台发布 create-react-app 部署实战从 build 产物到客户端路由回退与多平台发布【免费下载链接】create-react-appSet up a modern web app by running one command.项目地址: https://gitcode.com/gh_mirrors/cr/create-react-app本文围绕 create-react-app 仓库中的官方部署文档 deployment.md 展开系统讲解npm run build之后如何将build产物部署到静态服务器、集成到现有服务端应用以及如何正确处理 HTML5pushState客户端路由的回退问题。文中同时结合 react-scripts 构建脚本 与 webpack 配置 的源码解释homepage字段如何决定资源路径帮助读者独立完成从 GitHub Pages 到 Firebase、Netlify、Vercel 等多种发布方案。1. 部署的对象npm run build产出了什么npm run build会创建一个build目录其中包含应用的生产构建产物。部署的本质是让任意 HTTP 服务器做到两件事访问者打开站点时返回index.html对/static/js/main.hash.js这类静态路径的请求返回对应文件的实际内容。理解这一点后再来看构建脚本的源码可以确认build目录是如何被组装出来的。在 build.js 中构建流程依次是校验必需文件存在checkRequiredFiles([paths.appHtml, paths.appIndexJs])即public/index.html与src/indexbuild.js 第 50 行清空旧的build目录保留目录本身防止你正在该目录内时整个目录被移入回收站调用copyPublicFolder()把整个public目录合并进build运行 webpack 生产构建最后通过printHostingInstructions()打印部署提示。其中copyPublicFolder()的实现值得注意build.js 第 220-225 行function copyPublicFolder() { fs.copySync(paths.appPublic, paths.appBuild, { dereference: true, filter: file file ! paths.appHtml, }); }public目录里的所有文件favicon、manifest.json、robots.txt以及后文要讲的.htaccess、_redirects都会被原样拷入build唯独public/index.html被过滤掉——因为它只作为 HTML 模板最终由 webpack 的HtmlWebpackPlugin生成带资源引用的版本写入build。这也解释了为什么所有“把某个文件放进public/”的平台技巧Apache 的.htaccess、Netlify 的_redirects、GitHub Pages 的CNAME都能自动进入构建产物。构建完成后printHostingInstructions.js 会根据你的配置打印不同的提示如果homepage指向*.github.io且尚未配置deploy脚本会给出gh-pages的安装与脚本配置示例如果publicPath不是/会提示“构建假设站点托管在某个子路径”否则打印serve -s build这样的静态服务器启动建议。这就是文档中“运行npm run build后能看到一张部署 cheat sheet”的由来。2. 用静态服务器运行生产构建对于 Node 环境最简单的方式是全局安装 Vercel 出品的静态服务器servenpm install -g serve serve -s build最后一条命令会把静态站点部署在3000端口上。与serve的许多内置设置类似端口可以通过-l或--listen参数调整serve -s build -l 4000运行下面命令可以查看完整的可选项列表serve -h这里的-ssingle-page app 模式会让serve把未知路径都回退到index.html因此它天然支持客户端路由场景。3. 集成到现有服务端应用运行一个 create-react-app 项目并非必须依赖独立静态服务器它同样可以很好地嵌入已有的服务端应用。下面是一个基于 Node 和 Express 的程序化示例const express require(express); const path require(path); const app express(); app.use(express.static(path.join(__dirname, build))); app.get(/, function (req, res) { res.sendFile(path.join(__dirname, build, index.html)); }); app.listen(9000);服务器软件本身的选择并不重要create-react-app 是完全平台无关的没有必要显式使用 Node。build目录就是 create-react-app 输出的唯一产物任何能把静态文件映射到 URL 的服务器nginx、Apache、Caddy、S3……都可以承接它。但上面的配置对于使用客户端路由的应用来说还“差一点”。如果你希望在单页应用中支持/todos/42这类 URL请继续看下一节。4. 支持客户端路由HTML5 pushState 回退如果你的路由基于 HTML5 的pushStatehistory API 实现例如使用browserHistory的 React Router很多静态文件服务器会直接失败。以 React Router 中配置了/todos/42路由为例开发服务器能正确响应localhost:3000/todos/42但按上一节方式配置的生产 Express 服务器则不行。原因很直白当用户首次fresh load打开/todos/42时服务器会去寻找文件系统里真实的build/todos/42自然找不到。服务器需要被配置为对/todos/42的请求返回index.html。修改上面的 Express 示例对任意未知路径都返回index.html即可app.use(express.static(path.join(__dirname, build))); -app.get(/, function (req, res) { app.get(/*, function (req, res) { res.sendFile(path.join(__dirname, build, index.html)); });Apache HTTP Server使用 .htaccess如果你使用 Apache HTTP Server需要在public文件夹中创建一个如下内容的.htaccess文件Options -MultiViews RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.html [QSA,L]运行npm run build时它会随public目录一起被拷入build文件夹这正是第 1 节中copyPublicFolder()过滤逻辑要覆盖的场景。Apache Tomcat如果你使用 Apache Tomcat可以按社区中针对 Tomcat 回退配置的常见 Stack Overflow 方案处理通过 Tomcat 的 404 重定向机制把未知路径指向index.html。完成以上任一配置后对/todos/42的请求在开发和生产环境都会被正确处理。Service Worker 的导航回退PWA 场景在生产构建中如果应用已经按 making-a-progressive-web-app.md 文档选择启用 PWAservice worker 会自动处理所有导航请求例如/todos/42方式是直接返回缓存的index.html副本相当于在浏览器层完成回退。文档指出可以通过eject后修改SWPrecachePlugin配置中的navigateFallback与navigateFallbackWhitelist选项来定制或关闭这一导航回退。需要补充的是当前仓库的源码现状从 webpack.config.js 第 705-717 行 看react-scripts 目前已改用workbox-webpack-plugin的InjectManifest来生成 service worker且仅当项目存在src/service-worker.jspaths.swSrc时才生效而默认模板 cra-template/template 并不携带该文件即只有采用 PWA 模板opt-in的项目才会生成 service worker。因此对当前版本而言定制导航回退更直接的做法是修改自己编写的 service worker 源码或在 eject 后调整InjectManifest相关配置。修正 Web App Manifest 的 start_url当用户把应用安装到设备主屏时默认配置会让快捷方式指向/index.html这对期望应用从/开始提供服务的客户端路由器可能无效。请编辑public/manifest.json把start_url改为所需的 URL 方案例如start_url: .,值得一提的是当前仓库的默认模板 manifest.json 已经将start_url设为.相对路径新创建的项目在子路径部署时天然规避了这一问题。5. 相对路径构建与 homepage 字段默认情况下create-react-app 的构建假设应用托管在服务器根路径。要覆盖这个假设在package.json中指定homepage例如homepage: http://mywebsite.com/relativepath,这样 create-react-app 就能正确推断生成 HTML 文件时应使用的根路径。注意如果你使用react-router^4可以通过给任意Router传递basename属性来让Link相对它生成链接BrowserRouter basename/calendar/ Link to/today/ // renders a href/calendar/todayhomepage 是如何参与构建的源码级解析homepage的读取入口在 config/paths.js 第 26-30 行const publicUrlOrPath getPublicUrlOrPath( process.env.NODE_ENV development, require(resolveApp(package.json)).homepage, process.env.PUBLIC_URL );它把三个输入交给 getPublicUrlOrPath其解析规则可以归纳为PUBLIC_URL环境变量优先级最高如果设置了PUBLIC_URL直接使用它末尾自动补/homepage次之如果是完整 URL则只取它的 pathname 部分例如http://mywebsite.com/relativepath得到/relativepath以.开头的值有特殊待遇在生产模式下原样保留如.就是相对路径在开发模式下统一归一化为/因为开发服务器必须使用绝对路径都没有时返回默认值/。paths.js 第 20-25 行 的注释解释了为什么这一步必不可少webpack 必须知道应用被服务的根路径才能在 HTML 里写入正确的scripthref。不能简单用相对路径否则在把index.html作为/todos/42等嵌套 URL 的响应时浏览器会错误地去加载/todos/42/static/js/bundle.js。这个值随后在 webpack.config.js 中成为output.publicPathoutput: { // ... // We inferred the public path (such as / or /my-project) from homepage. publicPath: paths.publicUrlOrPath,同时它被注入为应用中的%PUBLIC_URL%index.html与process.env.PUBLIC_URLJavaScript见 env.js 的 getClientEnvironment。一个容易忽略的细节是当publicUrlOrPath以.开头相对路径时MiniCssExtractPlugin 的 publicPath 会被设为../../因为生产构建的 CSS 位于static/css目录下需要两级../才能定位到index.html所在目录。同一份构建部署到不同路径该能力自react-scripts0.9.0起可用。如果你没有使用 HTML5pushStatehistory API甚至完全不用客户端路由就没有必要在package.json里写明应用将被服务的 URL。取而代之可以这样写homepage: .,这会让所有资源路径相对于index.html。之后你可以把应用从http://mywebsite.com搬到http://mywebsite.com/relativepath甚至http://mywebsite.com/relative/path而无需重新构建。这正是上文源码分析中“生产模式下.被原样保留”这条规则的用途。6. 为任意构建环境定制环境变量你可以通过创建自定义.env文件并借助env-cmd来构建任意构建环境。以 staging 环境为例创建名为.env.staging的文件像普通.env文件一样设置变量例如REACT_APP_API_URLhttp://api-staging.example.com安装env-cmd$ npm install env-cmd --save $ # 或者 $ yarn add env-cmd在package.json中新增一个使用该环境构建的脚本{ scripts: { build:staging: env-cmd -f .env.staging npm run build } }现在运行npm run build:staging即可使用 staging 环境配置进行构建其他环境可依葫芦画瓢。.env.production中的变量会作为回退生效因为构建时NODE_ENV恒为productionbuild.js 第 11-13 行 在最开始就把NODE_ENV设为production。环境变量文件的加载顺序可以在 env.js 第 26-34 行 得到印证.env.production.local.env.local非 test 环境.env.production.env且 dotenv 永不覆盖已存在的环境变量——所以env-cmd提前注入的变量会优先于这些文件生效。另外注意 build.js 第 181-200 行当检测到CI环境变量为真时构建 warning 会被当作 error 处理source map 解析类 warning 除外这对在 CI 中执行上述构建脚本的团队是一个重要的质量闸门。7. 主流云平台部署方案AWS AmplifyAWS Amplify Console 为现代 Web 应用单页应用与静态站点生成器提供持续部署与托管附带 serverless 后端能力包括全球 CDN、自定义域名、特性分支部署和口令保护。登录 Amplify Console 控制台关联你的 create-react-app 仓库并选择分支也可以选用社区提供的 create-react-app Amplify 鉴权 starter 快速起步Amplify Console 会自动识别构建配置选择 Next选择Save and deploy。构建成功后应用即部署并托管在 amplifyapp.com 域名的全球 CDN 上后续每次向 Git 仓库提交代码都会触发前端或后端的持续部署。Azure Static Web AppsAzure Static Web Apps 基于 GitHub Actions 为 React 应用创建自动化的构建与部署流水线应用默认地理分布、多接入点PR 会自动构建出 staging 环境预览。在 Azure 门户创建新的 Static Web App填写信息并关联你的 GitHub 仓库确认 “build” 选项卡中构建文件夹配置正确然后创建资源。Azure Static Web Apps 会在你的仓库中自动配置好 GitHub Action 并开始部署路由、API、认证与授权、自定义域名等更多能力可查阅其官方文档。Firebase Hosting如果尚未安装 Firebase CLI先运行npm install -g firebase-tools。注册 Firebase 账号并创建一个新项目然后运行firebase login登录。在项目根目录运行firebase init选择Hosting: Configure and deploy Firebase Hosting sites选择刚创建的 Firebase 项目同意生成database.rules.json把build选为 public directory并在询问Configure as a single-page app时回复y。典型的交互输出如下 Project Setup First, lets associate this project directory with a Firebase project. You can create multiple project aliases by running firebase use --add, but for now well set up a default project. ? What Firebase project do you want to associate as default? Example app (example-app-fd690) Database Setup Firebase Realtime Database Rules allow you to define how your data should be structured and when your data can be read from and written to. ? What file should be used for Database Rules? database.rules.json ✔ Database Rules for example-app-fd690 have been downloaded to database.rules.json. Future modifications to database.rules.json will update Database Rules when you run firebase deploy. Hosting Setup Your public directory is the folder (relative to your project directory) that will contain Hosting assets to uploaded with firebase deploy. If you have a build process for your assets, use your builds output directory. ? What do you want to use as your public directory? build ? Configure as a single-page app (rewrite all urls to /index.html)? Yes ✔ Wrote build/index.html i Writing configuration info to firebase.json... i Writing project information to .firebaserc... ✔ Firebase initialization complete!重要你需要在firebase.json中为service-worker.js文件设置正确的 HTTP 缓存头否则首次部署之后你将看不到任何变更对应 create-react-app 仓库的历史 issue #2440。在hosting键内添加{ hosting: { ... headers: [ {source: /service-worker.js, headers: [{key: Cache-Control, value: no-cache}]} ] ... } }之后创建生产构建npm run build再运行firebase deploy即可部署 Deploying to example-app-fd690... i deploying database, hosting ✔ database: rules ready to deploy. i hosting: preparing build directory for upload... Uploading: [ ] 75%✔ hosting: build folder uploaded successfully ✔ hosting: 8 files uploaded successfully i starting release process (may take several minutes)... ✔ Deploy complete!GitHub Pages该能力自react-scripts0.2.0起可用。第 1 步在 package.json 中添加 homepage这一步至关重要如果跳过应用将无法正确部署。打开package.json为项目添加homepage字段。项目页project pagehomepage: https://myusername.github.io/my-app,GitHub 用户页user pagehomepage: https://myusername.github.io,自定义域名页homepage: https://mywebsite.com,create-react-app 使用homepage字段来确定构建后 HTML 文件中的根 URL见第 5 节的源码解析。第 2 步安装 gh-pages 并添加 deploy 脚本安装之后每次运行npm run build都会看到一份如何部署到 GitHub Pages 的 cheat sheet即 printHostingInstructions.js 检测homepage含.github.io/后打印的分支。要发布到https://myusername.github.io/my-app先安装npm install --save gh-pages或者使用 yarnyarn add gh-pages在package.json中添加以下脚本scripts: { predeploy: npm run build, deploy: gh-pages -d build, start: react-scripts start, build: react-scripts build,predeploy脚本会在deploy运行前自动执行。如果要部署到GitHub 用户页而非项目页还需额外修改把package.json脚本调整为推送部署到main分支scripts: { predeploy: npm run build, - deploy: gh-pages -d build, deploy: gh-pages -b main -d build,第 3 步运行 npm run deploy 部署站点npm run deploy第 4 步项目页需确认仓库设置使用 gh-pages 分支最后确保 GitHub 仓库设置中的GitHub Pages选项配置为从gh-pages分支提供服务。第 5 步可选配置自定义域名你可以向public/文件夹添加一个CNAME文件来为 GitHub Pages 配置自定义域名内容形如mywebsite.com关于客户端路由的说明GitHub Pages 不支持使用 HTML5pushStatehistory API 的路由器例如使用browserHistory的 React Router。因为当http://user.github.io/todomvc/todos/42这样含前端路由的 URL 发生首次页面加载时GitHub Pages 服务器不认识/todos/42会返回 404。如果你要在托管于 GitHub Pages 的项目中加入路由器有几种解决思路从 HTML5 history API 切换为基于 hash 的路由。如果使用 React Router可以改用hashHistory但 URL 会更长、更啰嗦例如http://user.github.io/todomvc/#/todos/42?_kyknaj。或者使用一种技巧让 GitHub Pages 把 404 重定向到带自定义 redirect 参数的index.html在部署前向build文件夹添加一个含重定向代码的404.html并在index.html中加入处理该 redirect 参数的代码。社区中有一篇名为 “spa-github-pages” 的指南详细解释了该技巧。故障排查“/dev/tty: No such a device or address”如果部署时出现/dev/tty: No such a device or address或类似错误尝试创建一个新的 GitHub Personal Access Token运行git remote set-url origin https://user:tokengithub.com/user/repo再次尝试npm run deploy。“Cannot read property email of null”如果部署时出现Cannot read property email of null尝试git config --global user.name your_namegit config --global user.email your_email再次尝试npm run deploy。Heroku使用面向 create-react-app 的 Heroku Buildpack基于 Node.js Buildpack 的零配置方案按官方博客 “Deploying React with Zero Configuration” 的步骤操作即可。Heroku 部署错误排查有时npm run build本地能成功却在 Heroku 部署时失败以下是最常见的两类情况。“Module not found: Error: Cannot resolve file or directory”如果看到类似remote: Failed to create a production build. Reason: remote: Module not found: Error: Cannot resolve file or directory MyDirectory in /tmp/build_1234/src意味着你需要确保import的文件或目录大小写与文件系统或 GitHub 仓库中实际一致。这一点很关键因为 Heroku 使用的 Linux 是大小写敏感的MyDirectory与mydirectory是两个不同的目录——即使本地能构建成功大小写不一致也会破坏 Heroku 远程构建中的import语句。“Could not find a required file.”如果你把必要文件排除或忽略在包之外会看到类似错误remote: Could not find a required file. remote: Name: index.html remote: Searched in: /tmp/build_a2875fc163b209225122d68916f1d4df/public remote: remote: npm ERR! Linux 3.13.0-105-generic remote: npm ERR! argv /tmp/build_a2875fc163b209225122d68916f1d4df/.heroku/node/bin/node /tmp/build_a2875fc163b209225122d68916f1d4df/.heroku/node/bin/npm run build此时请确保该文件以正确的大小写存在且没有被本地.gitignore或~/.gitignore_global忽略。这也与第 1 节中构建前的checkRequiredFiles([paths.appHtml, paths.appIndexJs])校验相呼应public/index.html与src/index.js缺失都会直接让构建失败。Netlify手动部署到 Netlify CDNnpm install netlify-cli -g netlify deploy选择build作为部署路径。配置持续交付这样配置后Netlify 会在你推送 git 或打开 pull request 时自动构建并部署创建新的 Netlify 项目选择你的 Git 托管服务并选中仓库点击Build your site。客户端路由支持要支持pushState请创建public/_redirects文件并写入如下重写规则/* /index.html 200构建项目时create-react-app 会把public文件夹的内容放入构建输出与 Apache.htaccess方案同一机制。VercelVercel 是一个 Jamstack 云托管平台即时部署、自动扩缩容、零配置提供全球边缘网络、SSL 加密、资源压缩、缓存失效等能力。第 1 步部署你的 React 项目。使用 Vercel 的 Git 集成时确保项目已推送到 Git 仓库通过 Import Flow 将项目导入 Vercel导入过程中所有相关构建选项都会为你预配置好也可按需修改。项目导入后所有后续推送到各分支都会生成 Preview Deployment而对 Production Branch通常是main或master的改动会生成 Production Deployment。部署完成后你会得到一个在线 URL。第 2 步可选使用自定义域名。在 Vercel 账号的 Domain 设置中添加或转移域名进入 Dashboard 中的项目页点击 Settings 选项卡下的Domains菜单项填入希望绑定的域名随后按提示选择 DNS 配置方式。对于全新的 React 项目还可以直接通过 Vercel 提供的 Deploy Button 一键部署Git 仓库会自动帮你建好。RenderRender 提供免费静态站点托管含完全托管的 SSL、全球 CDN 以及来自 GitHub 的持续自动部署按官方的 create-react-app 部署指南操作即可在几分钟内完成部署。S3 与 CloudFront将 React 应用部署到 AWS S3 CloudFront 的通行做法是npm run build后把build目录内容上传到 S3 桶并开启静态网站托管再由 CloudFront 作为 CDN 前置分发为支持客户端路由需要在 CloudFront 配置中把 404 错误页指向index.html。若还要附加自定义域名、HTTPS 与持续部署可以在此基础上配置 ACME/证书与 CI 钩子。社区中有两篇经典教程分别覆盖了“S3/CloudFront 基础部署”和“S3 HTTPS 自定义域名 CDN 完整指南”两种深度。Surge如果尚未安装 Surge CLI运行npm install -g surge。执行surge命令并登录或注册新账号。询问项目路径时务必指定build文件夹例如project path: /path/to/project/build注意为了支持使用 HTML5pushStateAPI 的路由器建议部署前把build文件夹中的index.html重命名为200.html——这样可以确保所有 URL 都回退到该文件Surge 原生支持200.html作为 SPA 回退页。8. 把组件发布到 npmcreate-react-app 本身不提供把组件发布到 npm 的内置能力。当你准备从项目中抽出一个组件供他人使用时官方建议是把它移出项目、放到一个独立的目录中然后使用如nwb之类的工具来准备发布。换言之create-react-app 的职责止步于“应用”的构建与部署组件库的封装应交给专门的工具链。小结build目录是唯一的部署产物public/全量拷贝index.html除外 webpack 编译输出serve -s build或任意静态服务器即可承接客户端路由的关键是让服务器对未知路径回退index.htmlExpress 通配路由、Apache.htaccess、Netlify_redirects、Surge200.html都是同一思想的变体homepage字段通过getPublicUrlOrPath决定output.publicPath与%PUBLIC_URL%设为.可获得跨路径免重构建的相对路径构建多环境构建用.env.stagingenv-cmd环境变量文件按env.js的固定顺序加载且不可覆盖已存在变量CI 下 warning 会被提升为 error各云平台方案GitHub Pages、Firebase、Netlify、Vercel、AWS、Azure、Heroku、Render、S3/CloudFront、Surge本质上都只是“构建目录 路由回退 缓存策略”三要素的不同组合。【免费下载链接】create-react-appSet up a modern web app by running one command.项目地址: https://gitcode.com/gh_mirrors/cr/create-react-app创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考