跳到主要内容

用 CloudBase 静态网站托管部署 React SPA

一句话定义:用 Vite + React 出 dist,通过 tcb hosting deploy dist -e <env-id> 一键上传到 CloudBase 静态托管,自动拿到 CDN 加速 + HTTPS + 自定义域名能力;React Router 用 BrowserRouter 时,在控制台把错误页面配为 index.html 解决刷新 404。

预计耗时:15 分钟 | 难度:入门

适用场景

这一篇覆盖的是最常见的 React 前端部署场景:纯 SPA、不要 SSR、不写后端逻辑,构建产物是一坨静态文件。CloudBase 静态托管底层是 COS + CDN,按存储和流量计费,没有容器实例费。

  • 适用:React 18+ + Vite 或 Create React App 出的 SPA
  • 适用:要部署在境内、域名要走 ICP 备案 + HTTPS
  • 适用:成本敏感、不需要服务端进程
  • 不适用:要 SSR / SSG / Next.js 服务端渲染——走云托管,参考 deploy-nextjs-to-cloudbase-run
  • 不适用:要 BFF 或后端 API——也走云托管或云函数

静态托管 vs 云托管

一句话区分:静态托管 = 纯前端文件 + CDN 分发;云托管 = 容器化的服务端进程

维度静态托管云托管
部署对象一坨静态文件(HTML/CSS/JS/图片)一个容器镜像(Dockerfile)
运行时无(CDN 直接回源 COS)Node.js / Python / 自定义运行时进程
计费存储 + 流量实例时长 + 流量
适合SPA / 文档站 / 落地页SSR / API / WebSocket / 长连接

React SPA 就两种产物:HTML 一份 + JS bundle 一份 + 各种静态资源,全部走静态托管最便宜。

环境要求

依赖版本
Node.js≥ 18(Vite 5/6 的最低要求)
React18+
Vite 或 CRAVite 5+ / Create React App 5+
react-router-dom6+(如用路由)
@cloudbase/clilatest(写文章时是 v3.x)
一个已开通的 CloudBase 环境含静态托管能力

CLI 全局安装并登录:

npm i -g @cloudbase/cli
tcb login

第一步:配置 React 项目

SPA 部署到静态托管,有两个配置必须先确认,否则到了第四步会出现刷新 404、资源 404 这类典型坑。

1.1 Vite 的 base 配置

vite.config.ts(或 vite.config.js):

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
plugins: [react()],
base: '/', // 部署到根域名时填 '/'
// base: '/my-app/', // 如果要部署到子路径(例如默认域名/my-app/)就填 '/my-app/'
build: {
outDir: 'dist',
},
});

base 决定 build 出来的 HTML 里资源引用的前缀。如果你部署到默认域名根路径(https://<env-id>.tcloudbaseapp.com/),就用 /;如果通过 tcb hosting deploy dist /my-app -e <env-id> 部署到子路径,就要改成 /my-app/前后斜杠都不能少

Create React App 项目对应的字段是 package.json 里的 homepage,含义类似(CRA build 产物在 build/ 目录而不是 dist/,下文以 Vite 为例)。

1.2 React Router 的 basename

React Router 6+ 有两种写法,basename 都必须和 Vite 的 base 完全一致

老写法(BrowserRouter

import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';

function App() {
return (
<BrowserRouter basename="/">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}

export default App;

新写法(createBrowserRouter,推荐)

import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';

const router = createBrowserRouter(
[
{ path: '/', element: <Home /> },
{ path: '/about', element: <About /> },
],
{ basename: '/' },
);

function App() {
return <RouterProvider router={router} />;
}

export default App;

两种写法等价,但 createBrowserRouter 支持 data API(loader / action),React Router 官方推荐新项目用它。

basename 跟 Vite 的 base 不一致会出现「首页能打开但跳转 /about 之后所有资源 404」这种诡异现象,这是 hosting-vue 一样的陷阱。

如果你只想图省事不踩 history 模式的坑,可以改用 createHashRouter(或 HashRouter),URL 会变成 /#/about 这种带 # 的形式,不需要任何服务端 fallback 配置。但 SEO 不友好,生产项目一般不推荐。

第二步:构建 dist

npm install
npm run build

跑完之后项目根目录会出现 dist/ 文件夹:

dist/
├── index.html
├── assets/
│ ├── index-a1b2c3d4.js
│ ├── index-e5f6g7h8.css
│ └── ...(各种 chunk 和静态资源)
└── favicon.ico

打开 dist/index.html 检查一下:所有 scriptlinksrc / href 应该都以你设的 base 开头。如果你的 base/my-app/,HTML 里就应该是 /my-app/assets/index-xxxxx.js,而不是 /assets/index-xxxxx.js。base 没改对的话回去改 vite.config.ts 重新 build。

第三步:部署到 CloudBase 静态托管

CloudBase CLI 提供两条部署路径,取决于你想要的工作流。

路径 A:tcb app deploy(推荐,自动化构建 + 上传)

tcb app deploy 会一条命令跑完 安装依赖 → 构建 → 上传产物 → 绑定路由 全流程,云端帮你跑 build:

tcb app deploy --framework react -e <env-id>

CLI 会从 package.jsonname 作为应用名,从 --framework react 推断构建命令是 npm run build、产物目录是 ./dist。第一次跑会交互式确认这些参数,之后会自动写回 cloudbaserc.json,下次只要 tcb app deploy 就够了。

参数清单(常用的):

参数说明
--framework框架类型:vite / vue / react / next / nuxt / angular / static
-e, --env-id <envId>目标环境 ID
--build-command <cmd>自定义构建命令,覆盖框架默认
--output-dir <dir>构建产物目录,Vite 默认 ./dist,CRA 是 ./build
--deploy-path <path>静态托管挂载路径,默认 /服务名

完整参数见 tcb app deploy 命令文档

路径 B:tcb hosting deploy(已有 dist 直接上传)

如果你想把 build 留在本地控制(比如 CI 已经跑过 npm run build),可以只上传 dist/

# 在项目根目录,先本地构建好
npm run build

# 部署 dist 整个目录到云端根目录
tcb hosting deploy dist -e <env-id>

# 或者部署到子路径
tcb hosting deploy dist /my-app -e <env-id>

tcb hosting deploy <localPath> [cloudPath] 是文件维度的工具,不会跑 install/build,只做上传。完整命令清单:

命令用途
tcb hosting deploy <localPath> [cloudPath] -e <env-id>上传文件/文件夹到指定路径
tcb hosting list -e <env-id>查看云端文件列表
tcb hosting detail -e <env-id>查看服务信息(默认域名 / 状态)
tcb hosting delete <cloudPath> -e <env-id>删除指定文件,加 --dir 删文件夹,加 --force 强删,加 --dry-run 预览
tcb hosting delete -e <env-id>清空整个静态托管

部署限制

  • 单个文件最大 50TB,文件数无上限
  • 上传遇到 socket hang up / ECONNRESET 大概率是 SDK 长连接被网络中间设备断了,先关掉重试:
    export COS_SDK_KEEPALIVE=false
    tcb hosting deploy dist -e <env-id>

完整命令文档见 CLI 静态托管命令

第四步:配 SPA fallback

这一步是 React Router BrowserRouter / createBrowserRouter 部署到静态托管的必配项,跳过这一步的症状:首页能打开,但是任何子路由(/about / /users/123)刷新或直接访问都会 404。

原因:history 模式靠 pushState 改 URL,但物理上云端只有 index.html 一个文件,访问 /about 时 CDN 找不到 /about.html,按默认逻辑返回 404。

修法:让 4xx 错误页面也回 index.html,把路由判断扔回前端。

操作路径:

  1. 进入 云开发控制台 → 静态网站托管
  2. 切到「设置」标签页
  3. 找到「错误页面」配置
  4. 把 4xx 错误页面填为 index.html
  5. 保存

配完之后访问 <默认域名>/about 会返回 index.html + 200 状态码(注意状态码,不是 404 改了响应体),React Router 拿到 URL 自己解析路由。

参考 静态托管控制台管理 文档的「重定向规则 → 错误码重定向」章节。

第五步:绑自定义域名 + HTTPS

默认域名 <env-id>.xxx.tcloudbaseapp.com 能用,但有访问频率限制,生产环境必须换自定义域名。

前置准备

  1. 域名 ICP 备案:境内访问的域名必须先在腾讯云完成 ICP 备案(个人备案约 7-20 天,企业差不多)
  2. SSL 证书:可在 SSL 证书控制台 免费申请 DV 证书,或上传已有证书

配置步骤

  1. 进入 云开发平台 → HTTP 访问服务
  2. 点「添加域名」,填自定义域名(例如 app.example.com
  3. 上传 SSL 证书或选已有证书
  4. CDN 类型选「云开发 CDN」(适合静态托管,自动配 CDN 加速)
  5. 添加成功后控制台会给一段 CNAME 值,复制下来
  6. 去 DNS 服务商(腾讯云 DNS / 阿里云 / Cloudflare 等)添加 CNAME 记录指向那段值
  7. 等 3-5 分钟 DNS 全球生效,控制台状态变「已生效」即完成

完整流程包含三种 CDN 类型对比,见 自定义域名

缓存配置(可选)

控制台「设置 → 缓存配置」,建议按资源类型分别配:

资源类型建议缓存时间
图片、字体30 天
CSS / JS(带 hash 后缀)7 天
index.html1 小时

index.html 故意配短,否则前端发新版本之后用户浏览器缓存的 HTML 还指向旧版本的 JS bundle 文件名,新部署不生效。

运行验证

# 1. 默认域名拿到首页(应该返回 200 + HTML)
curl -I https://<env-id>.xxx.tcloudbaseapp.com/

# 2. 直接访问子路由(配了 fallback 应该返回 200 而不是 404)
curl -I https://<env-id>.xxx.tcloudbaseapp.com/about

# 3. 静态资源(确认 base 配对了)
curl -I https://<env-id>.xxx.tcloudbaseapp.com/assets/index-xxxxx.js

浏览器侧再做一遍:

  1. 打开默认域名首页,路由跳转一遍 /about/users/123 都正常
  2. /about 页面直接按 F5 刷新——必须不能 404,这是验证 fallback 配对了的关键测试
  3. 自定义域名 DNS 生效后用 https://app.example.com 重测一遍

常见错误

现象原因修法
tcb hosting deploy ./dist -e xxxENOENT: no such file or directorydist 路径写错或当前目录没在项目根目录;CRA 项目产物在 build/ 不是 dist/cd 到项目根目录,确认 ls dist(CRA 是 ls build)能看到 index.html 再部署
首页能开,但刷新 /about 返回 404没配 SPA fallback(第四步)控制台「设置 → 错误页面」把 4xx 配为 index.html
首页打开后 CSS / JS 全 404,控制台 Network 看到资源路径不对vite.config.tsbase 跟实际部署的 cloudPath 不一致base 部署到根目录用 /,部署到子路径 /my-app 就用 /my-app/,改完 重新 build 重新部署
子路由刷新返回 200 但页面空白,Network 显示 JS 404BrowserRouter basename / createBrowserRouterbasenamevite.config.tsbase 不一致两边改成完全一样的字符串(//my-app/),重新 build 部署
CRA 项目部署后资源 404CRA 的 homepage 字段没改,build 出来的 HTML 资源路径不对package.json"homepage": ".""homepage": "/my-app",重新 npm run build
自定义域名访问 ERR_CERT_COMMON_NAME_INVALIDSSL 证书绑的不是当前域名,或上传的证书过期SSL 证书控制台 重申一份对的证书,重新绑定
自定义域名 DNS 解析返回还是旧 IPDNS 没切到 CloudBase 的 CNAME,或本地 DNS 缓存nslookup app.example.com 确认 CNAME 已指向腾讯云的 CDN 域名,本地 dscacheutil -flushcache 清缓存(macOS)
自定义域名 403域名没过 ICP 备案,或备案已过期ICP 备案系统 查备案状态,没备案就先备案
大批量上传时 socket hang up / ECONNRESETCOS SDK 长连接被网络中间设备断了export COS_SDK_KEEPALIVE=false 关长连接重试

部署相关的报错码可在 错误码总览 查到完整列表。

相关文档

下一步

React SPA 部署上线之后,前端调后端是自然延伸——LLM 代理放云函数避免浏览器暴露 key,参考 connect-openai-api-cloud-function;给 React 站点加 CloudBase 登录走 add-auth-web-with-cloudbase-sdk,复用同一个环境;CI 跨环境(dev / staging / prod)的密钥分层方案见 secure-secrets-in-cloud-function,配合 tcb hosting deploy 自动部署。