Cloudflare Pages 从部署到自定义域名的完整实操指南
先把话说在前面如果你只是想找个地方托管一个静态网站Cloudflare Pages 大概率不是你的第一选择但一旦把“部署 HTTPS DNS 缓存”这几件事串起来用一阵子你会很难再回到原来的工作流。我最近把个人博客从一台小服务器迁到了 Cloudflare Pages顺便把自定义域名也绑了上去整个过程远比我想象的顺滑但中间也踩了几个值得记下来的坑。这篇文章就把从部署到绑定全域名的完整过程拆开讲一遍包括每一处配置为什么这么填、底层发生了什么以及遇到问题时的排查链路。适合这几类人看刚接触静态托管的个人站长、想把博客从服务器搬走的开发者以及被 DNS 和证书搞到头大的半桶水运维。1. 为什么选 Cloudflare Pages免费托管背后的真实逻辑1.1 它和“把文件传到服务器”有什么区别传统静态网站托管最常见的一套组合是买一台云服务器装 Nginx 或 Apache把 HTML 文件传上去再手动申请证书、配置 HTTPS然后担心服务器到期、被扫描爆破、磁盘满了、证书忘了续……这套流程不是不能跑而是运维成本长期压在一个人身上。Cloudflare Pages 做的事情说穿了就是把“构建 → 托管 → CDN 分发 → HTTPS 加密”整个流水线自动化你只管把代码推到 Git 仓库剩下的构建、上线、全球节点加速、证书续期平台全部接手。Pages 部署完之后每个项目默认会拿到一个项目名.pages.dev的地址这个域名本身已经带 HTTPS 证书。也就是说哪怕你一个自定义域名都不绑光靠这个默认域名网站已经能以一个不错的访问速度跑起来了。它的资源是托管在 Cloudflare 全球边缘网络上的国内访问速度看具体地区和运营商但整体体验比单台小服务器强得多。免费的配额里没有带宽费用这一说静态站流量再大也不会收到天价账单这是它和传统服务器一个本质上的区别。1.2 选型对比Pages、Netlify、Vercel、GitHub Pages 怎么选静态托管平台现在不少每个都有人吹也都有各自适合的场景。我把几个主流选项放在一张表里做横向对比平台免费额度核心构建次数限制自定义域名与 HTTPS适合场景Cloudflare Pages无限带宽免费套餐500 次/月构建支持DNS 在同一平台时集成度最高静态站、SSG 站点、全球 CDN 需求Netlify月流量约 100GB300 分钟/月支持操作直观喜欢可视化操作、重度使用表单功能Vercel月流量约 100GB不限时间按次数计支持前端开发体验极佳Next.js、React 生态、Serverless 函数GitHub Pages仓库 1GB、流量约 100GB/月约 10 次/小时支持但 HTTPS 配置偶尔要等开源项目主页、个人简单页我的建议很直白如果网站是纯静态页面、博客、公司官网这类内容型站点Cloudflare Pages 的综合成本最低如果项目重度依赖 Next.js 的 SSR 或者其他 Serverless 函数Vercel 的开发者体验更完整如果只是想给开源仓库挂一个演示页面GitHub Pages 就够了没必要多绕一层。选型最忌讳的是“看别人推什么就用什么”先搞清楚自己是静态站还是动态站、需不需要函数计算、团队的代码托管在哪个平台答案基本也就出来了。1.3 一个容易忽略的价值和 Cloudflare DNS 的深度集成很多人把 Cloudflare Pages 当成“又一个静态托管工具”但我个人觉得它真正让人舒服的地方是当你把域名的 DNS 也托管在 Cloudflare 时Pages 和 DNS 之间是打通的。绑定自定义域名时系统会自动帮你创建 DNS 记录、自动签发 SSL 证书、自动配置边缘缓存。你不用去A平台的 DNS 控制台复制一串 CNAME 目标再跑到B平台粘贴等半小时解析生效再回到A平台点“验证”。这套集成逻辑在传统工作流里要用多个服务商手动拼起来在 Cloudflare 生态里就是点几下按钮的事情。这也是我后面第 4 章重点讲的内容因为很多教程只会教你“去添加 A 记录/ CNAME”却不说清楚为什么在 Cloudflare 托管域名时体验会完全不一样。2. 动手前需要先想清楚的三件事账号、仓库、域名2.1 Cloudflare 账号注册时这些信息先想好Cloudflare 的注册门槛很低一个邮箱就能搞定但实操中有几个细节容易在后期反过来卡住你。第一账号的邮箱和密码一定要保存在团队能接管的公共地方。Pages 的部署权限和 DNS 权限都在这个账号下面如果某天团队成员离职且没有交接整个站点的部署、域名解析都会变成黑盒。第二注册之后建议把“双重认证2FA”打开。Cloudflare 账号一旦被攻破里面的 DNS 配置可以被恶意篡改影响的不只是 Pages 这一个域名而是你在这个账号下托管的所有站点安全级别值得拉满。第三如果你只是单纯想用 Pages 部署静态站不一定要先添加“网站”资产到 Cloudflare。Pages 项目本身是独立于 DNS 托管资产的你甚至可以不把任何域名放进 Cloudflare单独用 pages.dev 域名发布站点。只有绑定自定义域名时才需要确认域名是否托管在这里。2.2 Git 仓库Pages 为什么偏爱“连接仓库”的方式Pages 支持两种部署方式连接 Git 仓库自动构建或者直接上传编译产物。我自己更推荐前者原因很简单——Pages 把部署这件事绑定到了 Git 的事件上每次 push 到生产分支自动触发一次构建每次创建 Pull Request自动生成一个预览环境每次回滚基于历史构建记录重新发布。这种“代码即配置”的模型是纯上传文件做不到的。仓库用 GitHub 或 GitLab 都可以Pages 在授权时会读取仓库列表你可以选择公开仓库或私有仓库。很多人担心静态站源码不想公开其实私有仓库完全没问题没有强制要求公开构建是在 Cloudflare 侧完成的不会把你的代码展示给访客。这里给一个最小的 Vite 项目目录结构做参考my-static-site/ ├── index.html ├── package.json ├── vite.config.js └── src/ ├── main.js └── style.css关键在于package.json里的build脚本和最终产物目录Pages 后续就是靠这两个信息来决定如何构建、发布哪个目录。2.3 自定义域名先明确域名托管在哪儿自定义域名的准备核心只有一件事确定你域名的 DNS 当前由谁托管。如果你是在阿里云、腾讯云、GoDaddy、Namecheap 这类注册商直接买的域名并且没有改过 Nameserver那么 DNS 默认由注册商托管。这种情况下绑定 Pages 有两种路线路线 A把域名的 Nameserver 改成 Cloudflare 分配的地址让 DNS 完全迁入 Cloudflare。路线 B不迁移 DNS保持注册商托管手动添加一条 CNAME 记录指向你的 Pages 项目。两条路线的区别和操作细节我放到第 4 章细讲。这里只需要记住一个判断原则如果域名只是为这一个站点服务强烈建议走路线 A因为后续所有 DNS 记录、证书、CDN 都在同一个控制台里管理省心不是一点半点如果这个域名下面还跑着邮件服务、其他托管平台的 A 记录、大量子域名那就要掂量一下迁移的波及范围保守起见可以先路线 B 跑通再说。3. 部署实操连接 Git 仓库与构建配置的关键细节3.1 创建 Pages 项目入口和三种方式登录 Cloudflare Dashboard 后现在的默认导航里已经能看到Workers Pages这个入口Pages 相关功能都收敛在左侧菜单里。点击“创建应用程序”或“创建项目”后Pages 部分一般会提供三种创建方式连接到 Git最推荐适合有长期维护打算的项目直接上传通过网页拖拽 zip 包或文件夹上传编译产物适合一次性部署Wrangler CLI通过命令行工具上传适合部署流程脚本化、自动化如果选择连接 Git系统会让你先授权 GitHub 或 GitLab 账号然后在仓库列表里选中目标仓库。这里有个小细节如果仓库列表里看不到想选的仓库多半是 OAuth 授权时只勾了部分仓库的权限去 GitHub 的 Applications 设置里把仓库权限放宽即可不用重新注册账号。3.2 Build 配置框架预设不是万能的很多教程让你选个“框架预设”就完事了但真实项目里有个好习惯是点开框架预设后手动核对构建命令和输出目录是否正确。Pages 的预设确实内置了常见框架的构建命令但它不完全等于你项目的真实情况——比如你用的不是框架默认目录或者版本差异导致的命令不同。我把几类常见项目的配置列一下可以直接对照抄项目类型构建命令输出目录Hexo 博客npx hexo generatepublicHugo 站点hugo --minifypublicVuePress 2 / VitePressnpm run docs:builddocs/.vitepress/distViteVue/Reactnpm run builddist纯静态 HTML留空/或留空有个容易被忽略的配置项根目录。如果代码仓库是 Monorepo站点代码在packages/website子目录下需要在这里填子目录路径否则 Pages 会在仓库根目录找package.json大概率直接构建失败。我第一次部署一个 Monorepo 项目时就被这个卡过一次项目结构长这样repo/ ├── packages/ │ └── website/ │ ├── package.json │ └── dist/ └── package.json这种情况下根目录要填packages/website构建命令填npm run build输出目录填dist。看起来很简单但如果不理解这个逻辑光看报错日志会非常困惑。3.3 第一次部署成功后的检查清单首次构建完成之后不要急着去绑定域名先打开项目名.pages.dev做一轮基础检查。我每次上线后都会按这几项过一遍页面能否正常打开控制台有没有红色报错静态资源路径是否正确——尤其要注意带 hash 的 JS/CSS 文件能否通过 CDN 正常加载F12 切到 Network 面板看有没有 404点击几个能跳转的链接看前端路由是否正常检查返回头里的cf-cache-status确认资源确实经过 CDN 缓存而不是后端直出如果用的是像 Vite、VuePress 这类构建工具默认会采用根路径/来引用资源一切正常。但我碰过有的团队手动改过base配置导致资源路径变成了相对路径或奇怪的前缀打开页面时 HTML 正常但静态资源全部 404。排查思路很简单打开页面源码看script标签的src和实际文件存放位置是否吻合。3.4 不连仓库的替代路线Direct Upload 与 Wrangler 命令连接 Git 仓库确实方便但并不是所有场景都需要它。你有没有遇到过这种情况客户发来个 .zip 包里面是设计稿切好的静态页面根本不需要后续持续更新或者你想部署一个内部工具页面不想把代码放到远程仓库。这时候就用得上直接上传和 Wrangler 了。直接上传是傻瓜式操作在 Dashboard 选择“直接上传”把包含 HTML/CSS/JS 的文件夹压缩成 zip直接拖进去Pages 会自动识别并发布基本不需要任何构建配置。这种方式推荐给完全不懂代码、只想把网站放上去的人。Wrangler 是 Cloudflare 官方命令行工具适合部署流程脚本化的场景。安装和部署核心就两条命令npm install -g wrangler npx wrangler pages deploy dist --project-namemy-static-site第二条命令里的dist就是你的输出目录--project-name可以指定项目名第一次执行时会让你确认是否创建新项目。部署成功后终端会返回一个pages.dev地址。这个方式有不少好处可以在 CI 里直接调用避免让 CI 系统去绑账号授权如果项目是自动化构建产出的每次构建完直接跑这一条命令就能发布体验非常丝滑。4. 绑定自定义域名DNS 与 SSL 证书的完整链路4.1 “添加自定义域名”背后到底发生了什么在 Pages 项目后台找到Custom domains自定义域名设置项输入你的域名比如www.example.com然后点继续。这里有一个重要的细节 Pages 不会因为你输入一个域名就能直接让全世界都通过它访问你的网站。它背后要做两件事在 DNS 里创建一条 CNAME 解析记录把www.example.com指向你的 Pages 项目默认域名比如my-static-site.pages.dev向证书签发机构申请一张覆盖该域名的 SSL 证书并在边缘节点部署如果你能理解这两件事后面遇到 SSL 状态卡在 Pending 或者 DNS 记录没有自动创建时就不会慌。Pages 的目标是让这个过程自动完成但自动的前提是你的域名 DNS 正好托管在 Cloudflare这样它才有权限替你去改 DNS。4.2 域名托管在 Cloudflare最顺滑的绑定方式如果你的域名已经接入 Cloudflare也就是域名当前的 Nameserver 是 Cloudflare 分配的那么绑定自定义域名几乎就是“输入域名 — 点激活 — 等待证书”三步。具体操作进入 Pages 项目 → 自定义域名 → 点击“设置自定义域” → 输入域名。如果输入的是www.example.com系统会检测到该域名已在 Cloudflare 账户内页面会提示“激活区域”确认后 Cloudflare 会自动创建一条 CNAME 记录目标指向项目默认 pages.dev 域名同时开始签发证书。这里有个常见疑问要不要同时绑定根域名example.com和www.example.com我的建议是两个都绑。绝大多数用户习惯输入根域名访问也有不少用户习惯带 www 的地址如果只绑其中一个另一个地址就会无法访问。把两个都绑上以后可以再通过_redirects文件做一次 301 跳转把 www 统一到根域名或反向统一这样既不掉权重也省得用户手动改地址。示例https://www.example.com/* https://example.com/:splat 301不过需要提醒你在 Pages 上加域名时根域名的 CNAME 在传统 DNS 体系里是不能作为“根记录”直接存在的Cloudflare 内部做了 CNAME 扁平化处理才让这个特性可用。如果你的域名 DNS 还托管在第三方注册商根域名的解析就没这么简单这也是我一直建议把 DNS 迁到 Cloudflare 的原因之一。4.3 域名不在 Cloudflare 时的两条曲线路线域名在第三方注册商的场景也没有想象中麻烦但有两条路要选清楚。路线 A把 DNS Nameserver 迁到 Cloudflare推荐长期使用首先在 Cloudflare 左侧菜单点击“添加站点”输入你的域名选择免费计划。Cloudflare 会给出两个 Nameserver 地址比如amy.ns.cloudflare.com和bob.ns.cloudflare.com。然后去你域名的注册商后台找到域名管理的 Nameserver 设置项把原来的两个默认 NS 替换成 Cloudflare 分配的这两个保存后等待生效。生效时间一般从几分钟到 24 小时不等DNS 的 NS 变更因为涉及全球递归节点的缓存刷新属于所有操作里最慢的一种。等 Cloudflare 控制台里该域名的状态从 “Pending Nameserver” 变成 “Active” 后你就可以像 4.2 节一样在 Pages 里一键绑定自定义域名。路线 B不动 DNS手动加 CNAME 记录快速但仍可控如果你不想迁移 DNS那就在第三方 DNS 控制台里手动添加一条解析。关键信息是记录类型CNAME主机记录填www如果直接解析二级域名就填对应前缀比如blog记录值填你 Pages 项目的默认域名比如my-static-site.pages.dev添加完成后回到 Pages 自定义域名设置页面点击“检查更新”或“重新验证”稍等片刻如果 DNS 生效且证书签发完成状态就会从 Pending 变为 Active。很多人在这里会尝试只绑定根域名。但第三方 DNS 的根域名记录类型受注册商限制想用 CNAME 指向 pages.dev 通常不被允许于是转而问“Pages 有没有 IP 可以用 A 记录指向”。Pages 和 Cloudflare 的 CDN 节点一样是基于域名动态分发的没有固定 IP 来给你填 A 记录所以根域名基本只能通过迁移 NS 到 Cloudflare 来解决这个限制在动手前就要清楚。4.4 SSL 证书的自动签发流程与状态解读Pages 为自定义域名签发的证书用的是 Let’s Encrypt整个流程对用户来说几乎是黑盒但有几个状态值得了解否则卡住时很容易一头雾水。状态一般有Pending和Active两种。Pending表示证书申请已在处理中CA 需要验证你对域名的控制权。证书签发会通过 DNS 验证或 HTTP 验证来完成所以 DNS 记录如果还没全球生效证书就会一直处于等待状态。一旦 CA 成功完成验证证书签发并部署到边缘节点状态就会变成Active。实际经验是大多数域名从添加自定义域到证书 Active 只需要几十秒到几分钟但 DNS 变更传播慢时偶尔会等上一两个小时。如果超过 24 小时还是 Pending先检查 CNAME 记录是否真的出现在 DNS 解析结果里其次确认有没有在同一个域名上添加重复或冲突的记录最后再考虑删除自定义域重新添加一次。绝大多数“证书卡住”案例根因都是 DNS 根本没生效而不是 Cloudflare 本身的问题。5. 部署之后绕不开的几个问题404、缓存、构建失败5.1 子路由 404SPA 的 fallback 处理把 Vue 或 React 写的单页应用部署到 Pages 后很多人会遇到一个奇怪现象首页打开一切正常点浏览器刷新/about页面时直接 404。原因是单页应用在客户端路由模式下只有index.html这一个真实入口访问/about时服务器找不到对应的about.html于是返回 404。解决方法是给 Pages 项目添加一个_redirects文件放在输出目录的根目录。内容写一行/* /index.html 200这条规则的意思很直白所有未匹配到真实文件的请求都返回/index.html并且状态码是 200。浏览器拿到index.html后再由 JavaScript 根据地址栏路径渲染对应页面。这里要提醒两点第一这个规则只适用于真正的 SPA 项目多页站点千万不要写否则所有 404 页面都会被吞掉第二这种 fallback 对 SEO 不友好搜索引擎爬虫请求/about时拿到的是index.html不是独立页面的内容如果对 SEO 有要求要么改用 SSG 预渲染要么换用支持 SSR 的部署平台。5.2 缓存不刷新CDN 缓存与浏览器缓存的博弈站点发版后用户打开却是旧页面这是所有 CDN 托管用户都会遇到的问题。要理解它得先分清楚缓存发生在哪一层。Pages 默认对带 hash 的静态资源JS/CSS 图片设置了较长的缓存时间因为文件名变了就代表内容变了浏览器会加载新文件而对index.html这类入口文件默认不会缓存太久这样才能尽快感知到发版。但如果你在项目里通过_headers文件手写了过激的缓存策略或者当地网络运营商额外缓存了一层就会出现发版后半天看不到新内容的状况。Pages 支持通过_headers文件自定义响应头我通常的做法是/assets/* Cache-Control: public, max-age31536000, immutable这个配置的核心思路是带 hash 的静态资源可以放心让浏览器缓存一年因为内容一变文件名就变不带 hash 的 HTML 文件保持默认策略不长期缓存。如果你遇到紧急发版需要立即让用户看到可以去 Cloudflare 控制台的“缓存”区域点“清除所有内容”这个操作会把边缘节点上的缓存清掉但不影响浏览器本地缓存。5.3 构建失败排查从日志定位到修复的完整链路构建失败不可怕可怕的是拿到一坨压缩后的日志不知道从哪看起。我总结了一套自己常用的排查链路按顺序走基本不会兜圈子。第一步打开项目里的Deployments列表找到状态为失败的这次部署点击查看构建日志。日志会展示整个构建过程失败原因通常就在最后几十行。第二步看错误类型。最常见的几类常见报错真实原因npm ERR! missing script: build项目根目录的 package.json 里没有定义 build 脚本sh: 1: hexo: not found依赖未安装或系统环境缺少对应命令可改为npx hexo generateFailed to find a default file in the output directory输出目录填错Pages 在里面没找到index.htmlNode.js version ... is not supported项目需要更高版本的 Node需在环境变量里指定NODE_VERSIONsrc refspec main does not match anyGit 仓库没有生产分支检查分支名和构建配置里的生产分支是否一致第三步根据错误信息做修复。比如本地能构建但 Pages 上失败的第一反应检查package-lock.json是否在仓库里、依赖源是否能访问、有没有使用需要额外密钥的私有依赖。Pages 构建环境默认是干净的不存在你本地已经装好了某些全局依赖这种隐藏前提。第四步修复后回到部署列表点击“重试部署”——Pages 支持对失败的部署重新触发不需要重新 push 代码这个功能在调试时很省事。5.4 自定义域名状态一直 Pending 怎么办域名绑定后状态长时间 Pending是我在社区里被问得最多的问题之一。排查链路其实很清晰第一确认 DNS 记录是否真实存在。打开终端执行dig www.example.com看返回的 CNAME 指向是否就是你的 Pages 项目域名。如果没有任何记录说明添加到 Cloudflare 后自动创建的 DNS 记录可能没成功回到 DNS 设置里手动加一条同名 CNAME 即可。第二确认域名的 Nameserver 状态。如果域名在 Cloudflare 里的状态还是 Pending Nameserver说明 NS 切换根本没完成此时 Pages 即使创建了 DNS 记录实际解析还是走的旧 DNS 服务商证书自然无法签发。第三检查是不是填错了记录值。很多人把 CNAME 的值写成了pages.dev本身正确写法应该是你的项目名.pages.dev这两者的区别非常关键。第四如果以上都没问题可以把自定义域名先删除等待几分钟再重新添加。重新添加会重新触发证书申请有时候比干等高效得多。6. 进阶玩法预览部署、回滚与多环境管理6.1 每个提交和 PR 都能生成一个预览地址Pages 的预览部署功能是我用过之后再也回不去的一个特性。当你往非生产分支 push 代码或者创建了一个 Pull RequestPages 会自动为这个代码版本生成一个独立的预览地址类似hash.project.pages.dev。这个能力在内容审核、多人协作、前后端联调场景里非常实用。比如博客要发一篇新文章还没准备好放线上直接把改动推到draft分支把预览地址丢给朋友看排版效果他们不需要碰任何命令行打开链接就是和线上完全一致的环境。而且预览环境的构建也会在部署列表里独立展示不会影响生产环境的稳定性完全隔离。6.2 一键回滚比本地还原快的版本回退Pages 的部署列表不仅仅是一个操作记录每一行都是一个可以重新发布的版本。线上出了严重问题想在几十秒内回到上一个正常版本直接在部署列表里找到上一次成功的部署点击“回滚”按钮Pages 会立即重新发布该版本不需要重新拉代码、重新构建、重新 push。这里有一个容易误解的点回滚只是把 Pages 当前发布的产物恢复到了某个历史构建它不会改变你 Git 仓库里的代码状态。换句话说你可以先回滚让线上恢复然后再从容地在本地修复代码推新版本上去两者是独立的这比传统服务器上“回滚要改代码重新部署”要灵活得多。6.3 环境变量与多环境管理同一个项目生产环境和预览环境经常需要用不同的配置。比如前端请求的 API 地址生产环境填正式服务器地址预览环境填测试服务器地址。Pages 在项目设置里提供了环境变量功能而且区分了生产环境和预览环境两组变量你可以单独为它们赋值。具体操作项目设置 → 环境变量 → 添加变量比如API_BASE_URL然后分别配置生产和预览的值。构建时 Pages 会把对应的值注入到环境里应用里通过import.meta.env.VITE_API_BASE_URL或process.env.API_BASE_URL读取即可。有一个细节容易踩坑修改了环境变量之后必须重新触发一次部署才会生效。很多人改完变量发现线上没变化根本不是变量写错了而是忘记重新构建页面上停留的还是旧环境的产物。这个问题我在刚开始用的时候也踩过值改了好几遍最后才发现是没重新部署。6.4 往后扩展与 Workers、R2 组合的更多可能Pages 定位是静态托管但这不意味着你的项目只能是一堆死文件。Cloudflare 生态里还有很多可以跟 Pages 串联的能力。比如 R2它是 Cloudflare 的对象存储服务兼容 S3 API可以把图片、附件等大体积文件从 Pages 项目里拆出去单独存到 R2然后通过自定义域名直接访问。为什么要这么做一个很实际的原因是 Pages 对单个文件大小有限制大约 25MB超大文件放 Pages 本身就不合适放 R2 更合理。再比如 Workers可以当作一个极轻量的 API 层处理表单提交、身份校验、数据读写等动态逻辑。Pages 出静态页面Workers 出动态接口两者在同一个 Cloudflare 账号下可以用同一个自定义域名做路由分发对个人项目和中小企业来说整套基础设施的月度成本可能接近于零。还有 Forms 这类第三方服务可以处理静态站点的表单需求但如果你已经在 Cloudflare 生态里用 Workers 写一个接收 POST 请求的接口也只要几十行代码灵活性更高。这个往后扩展的空间是 Cloudflare Pages 对比单一静态托管服务的一大差异化优势。最后说点个人感受。Cloudflare Pages 最让我舒服的其实不是免费而是它把部署、域名、证书、CDN 这几件原本分散在各平台的事整合到了一起省掉了很多以前手工运维的琐碎事。如果你只是部署一个静态网站又不想被服务器续费、证书到期、DNS 解析这些问题反复折腾建议直接试一次。第一次绑定自定义域名时看到 SSL 状态从 Pending 变成 Active 的那一刻心里还是有点踏实的。