← 返回资讯

静态站如何低成本上云

2026-06-09

你把一个 API 文档站或者产品落地页做完了,本地 npm run build 跑出一堆 HTML/CSS/JS,接下来往哪儿放这个问题,看着简单,其实是最容易被想复杂的一步。很多人下意识就去买一台服务器装 Nginx,装完才发现自己其实只需要一个能访问的静态文件目录,白白背上了一台机器的运维成本。这篇把两种最常见的低成本方案讲透——你该按什么标准来选,而不是先囤一台服务器再说。

几种方式

  1. 对象存储 + CDN:成本极低、免运维、自动加速。
  2. 服务器 Nginx 托管:可控、适合多站。

这两句话是结论,下面把它们拆开讲清楚”为什么”和”怎么做”。

对象存储 + CDN:为什么它是默认选项

纯静态站(没有登录态、没有服务端渲染、没有需要保密的接口密钥)本质上就是一堆文件,用户访问时只需要”就近拿到文件”。对象存储把这堆文件当 key-value 存好,CDN 在各地部署边缘节点缓存这些文件的副本,用户请求会被就近的节点直接命中返回,根本不用打回源站——这意味着你的”服务器”永远不会因为流量暴涨被打满,因为大部分流量在边缘节点就被拦下来了。这也是为什么它免运维:没有进程会挂,没有系统要打补丁,你唯一要做的事就是把新文件传上去。

同步文件到对象存储,用官方命令行工具最省心,以阿里云 OSS 的 ossutil 为例:

ossutil cp -r ./dist oss://your-bucket/ --update

--update 参数只上传本地比云端新的文件,避免每次全量重传浪费带宽和时间。腾讯云 COS 用 coscmd upload -r,AWS S3 用 aws s3 sync ./dist s3://your-bucket/ --delete--delete 会把云端多出来的旧文件一并清掉,适合你确定本地目录就是唯一真源的场景,但如果桶里还塞了别的东西,这个参数会把它删掉,务必先确认桶路径没用错)。

坑一:Content-Type 猜错,JS/CSS 加载失败

对象存储上传时会按文件后缀猜测 Content-Type,多数工具默认识别没问题,但遇到一些冷门后缀(比如 .mjs.wasm、字体文件 .woff2)容易猜错,猜成 application/octet-streamtext/plain。表现是浏览器控制台报类似这样的错:

Refused to apply style from 'xxx.css' because its MIME type ('text/plain') is not a supported stylesheet MIME type

页面能打开但样式全丢、脚本不执行,第一反应容易怀疑是打包配置出了问题,其实是存储层的 MIME 类型没设对。排查方法很直接:打开浏览器开发者工具 Network 面板,点开报错的资源,看响应头里的 Content-Type 是不是预期的类型。解法是上传时显式指定,比如 ossutil cp 支持 --meta 'Content-Type:application/javascript' 手动覆盖,或者干脆用 CDN/存储控制台里的”MIME 类型映射”功能把冷门后缀都注册一遍,一次配置,之后每次上传都生效。

坑二:改了文件,CDN 还是老版本

这是最容易让人怀疑人生的一个坑:文件明明传上去了,本地一看内容也对,但浏览器打开还是旧的。原因是 CDN 边缘节点缓存了旧版本,只要缓存没过期就不会回源拿新文件,你在源站的修改暂时是”不可见”的。解决办法有两种:一是提前设计好缓存策略,给 HTML 设短缓存或 no-cache(每次都问一下源站有没有更新),给带 hash 文件名的 JS/CSS(比如 app.3f8a1c.js)设长缓存(反正内容变了文件名也会变,长缓存完全没风险);二是发版后主动刷新,控制台或 API 都能触发:

aliyun cdn RefreshObjectCaches --ObjectPath "https://your-domain.com/index.html" --ObjectType File

养成”发版必刷 HTML”的习惯,比事后排查”为什么线上还是老页面”省心得多。

坑三:自定义域名 HTTPS 与 SPA 路由回退

用对象存储自带的域名访问一切正常,换成自己的域名却打不开,多半是 CNAME 没解析对或者 HTTPS 证书没绑定——自定义域名接入 CDN 需要先在 DNS 那边把域名 CNAME 到 CDN 分配的地址,再在 CDN 控制台上传或申请证书完成 HTTPS 绑定,两步都做完才算真正生效,中间任何一步漏掉都会表现为”打不开”或”证书不受信任”的警告。

如果你的站是前端路由的单页应用(React Router/Vue Router 那种,不是每个 URL 都对应一个真实文件),直接刷新非首页的地址会报 404——因为对象存储只认物理路径,/docs/intro 这个路径下根本没有对应的文件。解法是在 CDN 或存储的”404 回源规则”里把所有找不到的路径都重定向回 index.html,交给前端路由自己去解析当前 URL 该渲染哪个页面。这个坑几乎每个第一次上线 SPA 的人都会踩一次,提前配好能省一次深夜排查。

服务器 Nginx 托管:什么时候该选它

对象存储 + CDN 几乎是纯静态站的最优解,但下面几种情况你还是需要一台服务器:站点需要在同一域名下反代一部分动态接口(比如留资表单提交到自己的后端)、需要更精细的访问控制(IP 白名单、Basic Auth 挡住预发布环境)、或者你手上已经有一台服务器要顺便托管好几个站点,图省事就没必要为每个站单独接一套对象存储+CDN。

一份能打的静态站 Nginx 配置大致是这样:

server {
  listen 443 ssl http2;
  server_name your-domain.com;

  ssl_certificate     /etc/nginx/certs/your-domain.crt;
  ssl_certificate_key /etc/nginx/certs/your-domain.key;

  root /var/www/your-site/dist;
  index index.html;

  gzip on;
  gzip_types text/css application/javascript application/json image/svg+xml;

  location ~* \.(js|css|png|jpg|woff2)$ {
    expires 30d;
    add_header Cache-Control "public, immutable";
  }

  location / {
    try_files $uri $uri/ /index.html;
  }
}

几个容易被忽略但很关键的点:gzip on 一定要开,静态站的 JS/CSS 文本压缩率通常能到 70% 以上,不开等于白白多传几倍流量,条件允许的话换成 brotlingx_brotli 模块)压缩率还能再提一截,尤其对文本类资源效果明显。expires 30d 配合 immutable 是冲着带 hash 文件名的资源去的——明确告诉浏览器”这个文件内容不会变,不用每次都问服务器”,减少不必要的协商请求。最后一行 try_files $uri $uri/ /index.html 就是前面提到的 SPA 路由回退,Nginx 托管下这行等价于对象存储的”404 回源规则”,同样的问题,不同层解决。

一台机器要同时托管好几个静态站,用 server_name 区分虚拟主机即可,每个站一个 server 块,指向各自的 root 目录,互不干扰;证书也可以用同一张通配符证书或者分别签,取决于你的域名结构。

两种方式怎么选

维度对象存储 + CDN服务器 Nginx 托管
运维成本几乎为零,不用管进程和系统需要打补丁、盯日志、防攻击
单站成本按存储+流量计费,小流量站极低服务器本身有固定月费,不管有没有流量都在花钱
多站复用每个站独立计费,站多了成本线性涨一台机器能挂十几个站,边际成本趋近于零
弹性应对突发流量CDN 天然分担,几乎不用担心打满得看服务器带宽和配置上限,容易被打满
需要反代动态接口做不到,纯静态可以,Nginx 反代加几行配置就行
精细访问控制依赖 CDN 平台功能,能力有限Nginx 配置随便怎么写都行

简单说:只有一两个纯静态站、流量不确定、不想碰运维,选对象存储 + CDN;手上已经有服务器、要挂一堆站、或者需要反代/鉴权这类灵活控制,用 Nginx 托管更划算。别一上来就为了”可控”买服务器,等真的需要动态能力或者站多到值回服务器成本时再换也不迟。

进阶:CI/CD 自动部署,别再手动传文件

无论选哪种方式,手动执行上传命令这件事迟早会漏掉一步(比如忘了清 CDN 缓存),交给 CI 流水线更靠谱。GitHub Actions 部署到对象存储的核心步骤大致是:拉代码、跑构建、用云厂商提供的 Action 或命令行工具同步文件、最后调一次缓存刷新接口,四步写进一个 workflow 里,每次 push 到主分支自动跑完,人只管合并 PR。部署到服务器同理,用 rsync 加 SSH 密钥把构建产物同步过去,再触发一次 Nginx reload(改配置才需要 reload,纯换静态文件其实不用重启进程,直接覆盖文件即可生效,这点常被误解成”每次发版都要重启 Nginx”,其实没那个必要)。

自动化的另一个好处是环境一致:本地构建可能因为 Node 版本、缓存的 node_modules 不一样产出略有差异的产物,CI 里用固定版本的镜像跑,保证每次发布的文件都是同一套构建规则出来的,排查”为什么本地好好的线上不一样”这类问题会少很多。

成本大致怎么算

对象存储 + CDN 的账单通常拆成存储费和流量费两块,存储费按 GB/月计,流量费按出网流量计——静态站的成本大头几乎都在流量而不是存储,一个几十 MB 的站点存储费低到可以忽略,真正决定账单的是访问量乘以页面体积。做预算时按”月访问量 × 平均单次加载流量(含图片资源,通常几百 KB 到几 MB 不等)“估个流量总量,再乘以流量单价,就能拿到一个大致区间;具体单价各家浮动且经常调整,以官方定价页面为准(截至 2026-06 各大云厂商都有较低的入门档位和免费额度,小流量站的月成本通常在个位数到几十元之间)。服务器 Nginx 托管则是固定月费起步,流量常有一定额度或者单独计费,站少的时候单站均摊成本会比对象存储高,站多起来之后反而更划算,这也是前面对比表里”多站复用”那一行的由来。

自检清单

上线前照这个顺序过一遍,基本能避开前面提到的所有坑:

  1. 打开开发者工具 Network 面板,确认 JS/CSS 的 Content-Type 是对的,不是 text/plainapplication/octet-stream
  2. 直接在地址栏敲一个非首页的深层路径(比如某篇文章详情页)刷新,确认没有 404,而是正常渲染;
  3. 改一处明显的文字,发布后立刻访问,确认看到的是新内容,不是缓存的旧版本;如果不是,检查 HTML 缓存策略或手动刷新一次 CDN;
  4. 用自己的域名(不是云厂商默认域名)访问,确认地址栏是绿锁 HTTPS,没有证书警告;
  5. 用手机网络(不是 wifi)打开一次,感受一下真实的加载速度,而不是只在办公室 wifi 环境下测。

小结

内容站优先静态 + CDN;需要动态能力再上服务端。更多建站思路见 快车库