
最早我托管个人博客用的还是最传统的方式一台云服务器装好Nginx把build出来的静态文件丢到站点目录再配一下HTTPS证书和日志切割。说实话站点的访问量并不高但为了这堆静态文件我得定期处理Nginx配置、给证书续期、盯磁盘空间、防SSH爆破一年下来真正花在内容上的时间还没花在“维护服务器”上的时间多。后来我第一次尝试用Serverless方式部署静态站点把静态文件传到对象存储开启静态网站托管再挂上CDN和自定义域名。全程没有碰一台服务器页面打开速度反而比原来快了不少。也是从那次之后我把手上好几个个人项目全部迁到了这套架构上运维工作几乎降到了零。这篇文章就围绕“Serverless部署静态站点”这条主线把我实际部署、踩坑、调优的过程完整写一遍。内容既适合第一次接触Serverless的小白也适合想从传统服务器迁到Serverless的开发者参考。1. 静态站点而已为什么要扯上Serverless1.1 传统部署到底贵在哪很多人觉得一个静态站不就是一堆HTML、CSS、JS文件吗随便找台服务器扔上去就行何必绕一圈用Serverless。这个想法并没错但它只看到了“文件传输”这一步没看到文件后面跟着的一整串运维账。我自己的经历是一台1核2G的云服务器一年下来CPU跑不满5%内存闲置一大半但该花的钱一分不少。而且最麻烦的不是钱是时间系统漏洞要打补丁Nginx版本要升级证书三个月要换一次还要担心服务器被扫到之后的入侵风险。这些活儿本身没有任何“产出”它们只是静态文件能稳定被人访问的前提。Serverless方式把这一整串前提都抽走了。你的静态文件不再属于某一台机器而是被放到对象存储这类云服务里由云厂商保证它的持久性、可用性和吞吐能力。你所面临的“运行环境”问题直接从部署清单里消失。1.2 Serverless静态托管帮你省掉的隐形工作我用一个具体的场景来说。传统方案里如果网站某天访问量突然从100涨到10万你第一反应是加带宽还是加机器无论哪种都要经历“观察——决策——操作——生效”的链路等配置完成流量高峰可能已经过去了。Serverless方案没有类似的扩缩容动作。对象存储本身就是海量并发设计的CDN节点也会把你的静态文件分散缓存到离用户最近的边缘节点。用户访问的是CDN而CDN回源到对象存储也只发生在缓存未命中时。这套链路天然能扛住突发流量你不需要做任何弹性伸缩配置。另外有两件容易被忽略的事HTTPS证书和数据冗余。Serverless平台的证书通常是自动申请、自动续期源站存储的数据也会做多副本冗余单台磁盘损坏、甚至整个可用区出问题只要云厂商的容灾机制在文件就不会丢。这些在传统单机部署里都得自己操心的细节到了Serverless架构里几乎都是默认项。1.3 不适合Serverless化的反例帮你冷静判断讲完好处也得泼一盆冷水。Serverless部署静态站不是万能的下面几种情况你就得慎重站点后台需要执行服务端代码比如用户登录、数据库读写、支付回调这些不是“纯静态站”的范畴需要另外搭配Serverless函数或后端服务。如果你需要对文件做很细的读写权限控制比如登录用户才能下载某个文件那就要评估对象存储的权限系统和你的业务是否匹配而不是简单开启公有读。如果你所在的网络环境访问某些云服务本身就不稳定那Serverless实际体验可能反而不如自建服务器。判断标准其实就一条你的站点是不是“内容为主逻辑为辅”。只要核心是内容展示Serverless静态部署就大概率比传统服务器更合适。2. 部署前先回答三个问题区域、生命周期、访问特征2.1 区域与接入要求选存储桶地域前先确认的事第一次用Serverless部署时我图省事随意选了一个地域结果域名接入那一环得多走几步流程。后来我养成了习惯动手之前先把区域和接入要求问清楚。对象存储的存储桶是有地域概念的。你选的地域决定了后续CDN回源的物理距离也决定了这个存储桶本身所在的数据中心位置。通常建议遵循两个原则你的用户主要在哪存储桶就选在哪。国内访问为主就选国内地域海外访问为主就选海外地域。如果同一份数据有可能被多地用户访问不要靠一个存储桶硬撑应该依赖CDN的多节点覆盖来解决“距离”问题。CDN会负责把内容分发到各边缘节点源站存储桶的地域选择影响会小很多。域名接入这件事也要提前考虑。使用自定义域名绑定存储桶和CDN时不同地域、不同服务商对接入流程的要求并不完全相同有的需要在国内完成域名接入登记有的则不需要。我自己的做法是在创建存储桶之前先把服务商官方文档里“静态网站托管”和“自定义域名绑定”两个章节通读一遍确认没有特殊限制再往下走省得资源建好了又推倒重来。2.2 内容生命周期纯静态还是“静态为主、少量动态”第二步要分清你网站的“动态成分”到底有多少。如果纯静态那内容永远是那一堆文件如果是“静态为主、少量动态”就得在设计时预留出动态数据的接入方式。举个例子我的一个项目是文档站文档正文是静态的但“搜索”功能是动态的。早期我用的是一个第三方的站内搜索脚本直接在浏览器端索引全文不需要后端。后来内容变多变复杂搜索质量不够了我就把搜索拆到Serverless函数里由函数去查一个独立的索引服务。静态页面还是那些静态页面但页面右上角的搜索框已经指向了动态接口。这种“静态外壳少量动态接口”的组合是Serverless架构下非常推荐的做法。它保留了静态站的性能和成本优势又通过函数计算补足了动态能力。你在规划时不妨把站点的功能拆一遍哪些能纯静态实现哪些必须动态分清楚之后再选型就不会犹豫。2.3 成本边界从账单反推你的流量模型很多人对Serverless有个误解觉得它一定便宜。其实它的成本模型是“按量付费”流量大了、请求多了账单自然就上去了。静态站点的成本大头一般来自三块存储费用、CDN流量费用、请求次数费用。我自己的经验是一个日活几百上千的个人博客月成本通常保持在几元到几十元这个量级确实比云服务器便宜得多。但如果你做一个图片分享站文件又大又多用户访问频繁CDN流量费用很快就会成为大头。这时候就要做成本优化比如图片统一压缩、转WebP格式减小体积CSS、JS做合并和压缩文件版本化与CDN长缓存结合尽量让资源在用户本地就命中缓存减少回源和CDN节点反复拉取定期清理存储桶里的旧版本文件避免无用文件持续吃存储费用。成本模型应该是选型的一部分。先估算一下文件总量、月均访问量、平均资源体积再套到服务商的价格计算器里得到的数字基本就是你未来的月账单区间。3. 核心部署实操存储桶、CDN、域名三步走3.1 第一步创建存储桶并开启静态网站托管以主流的对象存储服务为例比如腾讯云COS、阿里云OSS。进入控制台后创建一个存储桶创建时有几个关键选项需要注意地域按上面说的就近原则选访问权限如果是公开站点建议选择“公有读私有写”也就是文件可以被任何人读取但只有你有权限写入版本控制个人站点可以不开如果你需要防止误删可以考虑开启但会产生额外的存储版本费用静态网站托管创建完成后需要在“存储桶配置”里的“基础配置”或“静态网站”相关菜单中打开开关。静态网站托管的开关里通常有一项索引文档设置默认是index.html也就是用户访问目录自动加载的文件这个一定要填。还有一个错误文档设置默认填404.html这个建议单独做一个带站点风格的美化404页面而不是用浏览器默认的文本错误页。这里有一个很重要的区别开启“静态网站托管”后的访问域名和你直接用“默认域名”访问存储桶里的文件走的是两套逻辑。托管模式下URL会按照目录索引自动加载index.html而普通模式下访问/默认会直接报错或列出文件列表取决于服务商策略。要让站点像一个真正的网站必须确认打开的是静态网站托管模式的域名。3.2 第二步上传站点文件并设置正确的访问权限本地构建好站点之后上传方式有控制台上传、API上传、命令行工具上传和CI/CD上传几种。我推荐命令行工具因为可重复执行、可脚本化。以腾讯云COS的命令行工具coscmd为例# 安装 pip install coscmd # 配置密钥和存储桶信息 coscmd config -a 你的SecretId -s 你的SecretKey -b bucket-appid -r region # 递归上传dist目录下的所有文件到存储桶根目录 coscmd upload -r ./dist/ / # 如果需要清理旧文件注意会删除所有远端文件先确认。 coscmd delete -r / --force阿里云OSS对应的命令工具是ossutil命令格式略有差异但逻辑相同先配置凭证再递归上传。关键的权限设置在上传前后都要检查一遍。如果你创建存储桶时选了“公有读私有写”上传之后文件自动就具备公开访问权限不需要再单独为每个文件设置ACL。这里比较容易出问题的点在于文件权限和存储桶权限不一致。比如存储桶是公有读的但个别文件可能继承了私有的权限导致部分图片或资源访问403。上传完成后建议用浏览器逐个访问几个关键资源确认HTTP状态码是200而不是404或403。3.3 第三步绑定CDN加速与自定义域名存储桶的托管域名通常不太好记而且没有CDN加速。生产环境建议绑定自定义域名并且接CDN。CDN配置里有两层回源设置要理解清楚CDN节点从源站拉取文件然后缓存到边缘节点用户请求先打CDNCDN只有在缓存未命中时才回源。所以这里的“源站”指向上一步创建的存储桶静态网站托管地址而不是你之前那台服务器。控制台里创建CDN加速域名时选择“静态网站源站”类型填上存储桶的托管域名回源协议建议直接选HTTPS回源。然后按照CDN服务商的要求在域名DNS服务商处添加一条CNAME解析到CDN分配的域名等解析生效后访问你的自定义域名就正式走在Serverless链路上了。到这一步你可能还会遇到证书问题。自定义域名要支持HTTPS需要在CDN控制台申请或上传SSL证书。现在主流平台都支持免费证书自动申请选择自动验证方式后平台会利用你的DNS解析完成域名所有权校验证书可以自动续期基本不需要人工干预。3.4 功能验收清单部署完成后先跑这五条我在每次部署完之后都会按下面这个清单挨个验证表面上看起来都是“打开页面看看”实际上每一条背后验证的都是不同的链路访问https://你的域名/是否正常加载首页浏览器地址栏显示小锁标识直接访问https://你的域名/about/这类二级路径刷新后页面是否正常这一步验证的是SPA路由或目录索引规则见下一节控制台“Network”面板里JS、CSS、图片等静态资源的HTTP状态码是否为200并且有正确的Content-Type查看CDN日志或回源统计确认哪些请求发生了回源、是否都在预期范围内用一个不存在的URL访问确认能返回一个美观的404页面而不是一堆XML堆栈或空白页。如果这里第五项没有通过别急着调CDN先去看存储桶静态托管配置里的“错误文档”是不是没填。这个验收清单看着琐碎但它能在你正式对外推广站点之前把线上问题提前拦下来。4. 最容易翻车的四个配置点我挨个踩过4.1 SPA前端路由刷新404错误文档的最佳实践第一次把Vue Router的History路由站点部署到Serverless上时我遇到一个经典问题从首页点进二级页面正常但用户在二级页面按一下F5刷新直接404。原理不复杂刷新二级页面时浏览器向服务器请求/about这个路径但存储桶里根本没有名为about的文件。传统Nginx可以通过try_files $uri /index.html把所有路径重写到index.html而Serverless静态托管的做法通常是利用“错误文档”机制把错误文档设置为index.html这样遇到不存在的路径时平台会返回index.html的内容前端Router接管后再渲染对应页面。这个方案能解决刷新404但也有个副作用真的404请求也会拿到200状态码并加载首页。所以应用代码里要用前端路由的兜底逻辑判断URL是否匹配具体页面不匹配就展示“内容不存在”的组件。另外如果你在意SEO这种“所有路径都返回index.html”的方式会让搜索引擎难以区分真实页面和错误页面更优的做法是部署时给每个路由生成独立的HTML文件预渲染把真实404页面单独保留为一个HTML并配为错误文档。4.2 HTML缓存时间过长导致发版不生效静态资源可以放心做长缓存但HTML文件千万别一上来就设成一年。我踩过一次最难受的坑改了一条导航文案执行完发布流程页面刷了很多次都没变化。F12一看index.html的Cache-Control是max-age31536000CDN节点把旧版本缓存得结结实实。这里需要区分两种资源带版本号的静态资源如app.8f3a2b.js文件名变了就是一个新URL可以做超长缓存完全安全不带版本号的HTML文件建议设置为不缓存或极短缓存如max-age0, s-maxage60保证用户能尽快看到新内容。如果你在构建工具里已经给静态资源加了hash那HTML文件里引用的URL每次发版都会变化这时候哪怕CDN缓存了旧HTML也只要等它过期或手动刷新一下就能恢复。发布流程里我一般会加一步“强制刷新CDN缓存”把HTML文件所在目录的缓存全部清理掉顺便再触发一次预缓存让边缘节点优先拉取一份新版本这样发布完成后第一批用户访问也不会命中旧缓存。4.3 CDN与源站的HTTPS回源问题混合内容与证书不一致CDN开启HTTPS之后还有一个容易被忽略的环节CDN节点到源站之间的回源协议。如果CDN用的是HTTPS源站也要求HTTPS但回源协议配的是HTTP中间就可能出现内容和协议不一致的情况。更常见的现象是混合内容阻塞页面是通过HTTPS加载的但HTML里某个图片或脚本却是http://开头的地址浏览器会直接阻止这个不安全请求。排查方法是在控制台里过滤Mixed Content警告把静态页面里所有资源引用改成相对路径或https://。我现在的做法是三步走构建阶段全站资源走HTTPS链接CDN开启强制HTTPS跳转回源协议直接选HTTPS。这三步配置好基本不会出现混合内容问题。另外如果站点有WebSocket长连接需求要提前确认CDN是否支持WebSocket代理不支持的话这部分流量需要绕过CDN直连源站或改走其他方案。4.4 防盗链配置误伤Referer空值和同源资源给站点加了防盗链之后我发现搜索结果页里用户点击跳转进来时部分图片加载失败。查了半天问题出在“允许空Referer”这个选项没开。防盗链通常是根据请求头里的Referer字段判断来源域名。但很多场景下Referer是空的浏览器直接输入URL、从本地文件打开、某些隐私模式以及部分App内嵌浏览器。如果防盗链配置把空Referer也拦截了就会把这个正常用户的图片请求拒掉。另外如果你在页面里引用了字体文件、跨域图片等静态资源CDN需要返回正确的Access-Control-Allow-Origin头否则字体可能加载失败。我当时的配置是“允许来源域名列表里加上自己的CDN域名和主域名同时勾选允许空Referer”Reefer白名单之外的情况一律拒绝。这样既能防住外部网站随便盗用图片流量又不影响正常用户和站内资源加载。5. 静态站点站上Serverless后的进阶玩法5.1 用边缘函数做更细的流量控制静态站点上了CDN之后很多人以为就结束了。但如果你用的CDN平台支持边缘函数有的叫Edge Function有的叫Function at Edge那么你可以在CDN节点上直接执行轻量代码实现很多原来要专门部署后端才能做的事。我自己用得比较多的是几个场景一是给响应统一加安全响应头比如Content-Security-Policy、X-Frame-Options、Strict-Transport-Security这些头在边缘节点注入不用改源站代码二是做灰度发布辅助通过判断用户请求头里的版本标识把一批用户引到新版本的存储前缀下另一批用户继续访问旧版本验证没问题再切全量三是简单的访问控制比如拦截异常UA或针对某些频率过高的IP做临时限速。边缘函数的成本通常很低因为只在边缘节点轻量执行时长按毫秒计费。如果你是第一次接触建议从一个最简单的“给响应增加Header”开始跑通流程之后再逐步增加逻辑。注意不要在边缘函数里做太重的IO或计算它不适合跑业务逻辑只适合做请求处理链路上的轻量干预。5.2 接入CI/CD让静态站持续发布部署静态站点最大的好处之一就是发布简单而进一步的好处是可以把发布做成全自动。我现在维护的文档站只要往Git仓库的主分支推一次代码流水线就会自动完成拉代码、构建、上传对象存储、刷新CDN缓存整个过程大概两三分钟。流水线怎么配取决于你的代码托管平台和云厂商。我用的方式是在仓库根目录放一个CI配置文件定义好触发器。核心步骤其实就是把第三章节里的上传命令搬到流水线里另外加上“构建阶段”和“刷新CDN”这两步。# 伪代码具体语法按你使用的CI平台调整 name: deploy on: push: branches: [ main ] jobs: build-and-deploy: runs-on: ubuntu-latest steps: - name: Checkout run: git checkout ${{ github.ref }} - name: Install and build run: npm ci npm run build - name: Upload to bucket run: coscmd upload -r ./dist/ / - name: Purge CDN cache run: curl -X POST https://cdn.api.example/purge?urlshttps://yourdomain.com/这个流程跑通之后你的手就完全从发布动作里解放出来了。以后改内容只需要管“写对提交”剩下的事都交给流水线。对于多人协作的项目还可以加上“推送测试分支触发预览部署”即每次MR都自动生成一个独立的预览环境地址方便团队评审。5.3 预渲染解决纯静态站的SEO短板纯静态站的SEO短板主要体现在SPA站点上。搜索引擎爬虫虽然近些年对JavaScript的解析能力提高了但并非所有场景都稳定而且首屏渲染依赖JS会让首次内容到达时间变长。对于内容型网站我更推荐预渲染。预渲染的思路很简单构建时把每个路由对应的页面都预先渲染成独立的HTML文件用户和爬虫拿到的都是服务端已经渲染好的内容。现在主流框架都有现成的预渲染方案比如VuePress、Astro、Next.js的静态导出模式基本能做到“写完Markdown自动生成全站静态页面”。如果你用的是Vue或React这类框架也可以在构建阶段额外安装预渲染插件指定一份路由清单由插件把每个路由爬一遍并生成对应的HTML。代价是构建时间会变长路由多了之后效果比较明显。但SEO的收益是值得的页面标题、描述、内容正文都直接出现在HTML源码里社交分享时的卡片也能正确抓取摘要。5.4 基础设施即代码把整个Serverless栈管起来最后说一个适合进阶用户的玩法用基础设施即代码IaC管理你的存储桶、CDN、回源规则、缓存配置。工具方面最常用的是Terraform主流云厂商也都有自己的IaC产品。我第一次把Serverless静态站点用Terraform重构时最大的感受是“可回滚”。以前在控制台里改缓存配置误操作了只能凭记忆恢复现在所有配置都写在一个.tf文件里执行terraform apply之前可以先用terraform plan看变更不满意直接改代码重来。# 伪代码示例具体用到的provider和resource以云厂商文档为准 resource cos_bucket static { bucket my-static-site website { index_document index.html error_document 404.html } } resource cdn_domain static { domain www.example.com origin { type cos domain cos_bucket.static.website_endpoint } https_config { switch on } }这套玩法非常适合多站点管理。我有好几个子站点配置结构几乎一样只是域名、存储桶名字不同。用IaC之后新增一个站点只需要复制一份配置模板改几个变量跑一次apply就能全部建好效率比在控制台里一个个点击高出一个量级。在我最初把Serverless部署静态站点当作“高级玩法”来研究的那段时间完整跑通一遍大概花了一整天大部分时间都耗在理解“错误文档”和“CDN缓存刷新”上。后来多部署了几个项目同样的流程走熟了之后基本十分钟内就能把一个新站点从零推到线上。这也正是Serverless架构的魅力当服务器、证书、扩容这些问题都不再需要你关心你要做的就是专注于内容本身以及偶尔在CDN配置页里点几个按钮。如果你正准备把现有站点迁过来我的建议是先拿一个低风险的子站点试水把整套流程跑通、记录下自己的验收清单再逐步迁移核心站点。踩坑不可怕可怕的是每次都在同一个坑里重复浪费半天时间。希望这份实操记录能让你少走几段弯路。