前置知识: Astro

Astro 构建与部署

14 min中级

旅程驱动理解从构建到上线:astro build、产物分析、适配器与按需渲染、CI 部署、域名与 HTTPS、路由缓存

知识点地图

  • 知识类别:Astro 构建与部署——astro build 产物、适配器与运行模式、SSR/静态取舍、路由缓存与 CI/CD,对应 Astro 官方文档「Deploy your site」系列。
  • 解决什么问题:源代码到「全球可访问网站」之间的全部工程环节:构建产物长什么样、跑在什么运行时上、静态与按需渲染怎么选、部署流水线怎么搭。
  • 什么时候用到:第一次部署 Astro 站点;给内容站接 CI 门禁;排查「本地好好的、部署就白屏」;SSR 站点的缓存策略落地。
  • 本篇主线:「书籍出版」六站旅程;核心决策只有两个——运行模式(第 3-4 站)与缓存策略(第 5 站),其余都是流程。错误选型的代价见第 4 节末的对照表。
  • 本篇不讲:Sessions 与 Server Islands 的运行时细节(见 063 篇、131 篇,本篇只讲它们对适配器的要求);环境变量的完整规则(见 135 篇)。

0. 开篇:一本书从手稿到书店的旅程

假设你写完了一本书的手稿。接下来会发生什么?手稿要送到印刷厂排版印刷,印好的样书要送去质检,然后你要选一家出版社(决定书的发行渠道),再决定这本书卖给谁、放在哪个书架的哪个位置,最后由物流车队把书送到全国书店,书店挂上招牌正式营业。每一站都有明确的任务,跳过任何一站,书就到不了读者手里。

网站上线是同一趟旅程。本文按”从构建到上线”的完整旅程组织,你跟着走一遍,就能理解 Astro 站点是如何从源代码变成全球可访问的网站的:

第 1 站 印刷厂:astro build(构建产物)

第 2 站 质检:产物分析(dist/ 里有什么)

第 3 站 选出版社:适配器与运行模式(静态 vs 按需渲染)

第 4 站 决定卖给谁:SSR 与静态的取舍

第 5 站 物流车队:CI/CD 与平台部署

第 6 站 挂牌营业:域名、HTTPS 与检查清单

第 1 站 印刷厂:astro build(构建产物)

第 2 站 质检:产物分析(dist/ 里有什么)

第 3 站 选出版社:适配器与运行模式(静态 vs 按需渲染)

第 4 站 决定卖给谁:SSR 与静态的取舍

第 5 站 物流车队:CI/CD 与平台部署

第 6 站 挂牌营业:域名、HTTPS 与检查清单

mermaid diagram

1. 第 1 站,印刷厂:astro build

1.1 构建在做什么

astro build 是 Astro 的”印刷车间”。它分两步完成印刷:

  1. 打包:把站点的页面、内容与客户端组件(岛屿)打包成 JavaScript;
  2. 生成:像运行一台小型服务器一样执行打包结果,为每个预渲染页面发起请求、渲染出完整 HTML,输出到 dist/ 目录。
bash
npm run build
# 构建完成后:
npm run preview   # 本地模拟生产环境,预览 dist/ 产物的实际效果

preview 值得养成习惯:它跑在构建产物之上,能发现”开发时正常、构建后异常”的差异(例如绝对路径、资源引用问题)。

1.2 构建产物长什么样

一次构建结束后,dist/ 大致是这样的结构:

dist/

index.html(首页,完整静态 HTML)

about/

index.html(目录格式页面)

posts/

hello-world/

index.html

_astro/(哈希命名的静态资源)

index-abc123.css(打包后的 CSS,含 scoped 规则)

hero-7f3k9a.webp(优化后的图片)

counter-xyz789.js(岛屿组件的客户端脚本,按需加载)

dist/

index.html(首页,完整静态 HTML)

about/

index.html(目录格式页面)

posts/

hello-world/

index.html

_astro/(哈希命名的静态资源)

index-abc123.css(打包后的 CSS,含 scoped 规则)

hero-7f3k9a.webp(优化后的图片)

counter-xyz789.js(岛屿组件的客户端脚本,按需加载)

mermaid diagram

1.3 构建提速:Astro 7 的”新印刷机”

Astro 7 把印刷车间整体换成了原生性能:.astro 编译器重写为 Rust、Markdown 管线替换为 Rust 实现的 Sätteri、打包器升级为 Vite 8(内含 Rust 写的 Rolldown)。官方基准测试显示,真实站点构建提速 15%-61%,其中 Markdown 密集的文档站收益最大——例如官方文档站(约 6313 页)从 114.5 秒降到 73.5 秒,Cloudflare 开发者文档站(8431 页)从 386.9 秒降到 261.9 秒。对你来说,构建命令和产物格式完全不变,只是等的时间变短了。

2. 第 2 站,质检:读懂产物

书要质检,产物要分析。构建完成后做三件事,能避免大部分”上线才发现”的意外。

2.1 看结构:页面是否都在

逐个核对 dist/ 中是否生成了所有预期页面(含动态路由展开后的全部实例)。漏页通常是 getStaticPaths() 返回的数据为空导致的。

2.2 看体积:有没有”意外的大包”

重点检查 _astro/ 中最大的几个文件:

bash
# 查看产物中各文件体积(按从大到小排序,前 20 个)
npx serve dist   # 或用任意静态服务器,打开 DevTools Network 观察
  • 出现巨型 .js:八成是某个岛屿误用了 client:load,或第三方库被整包打入。回到 006 的水合决策,把不紧急的岛屿降级为 client:idle/client:visible;
  • 出现巨型图片/字体:检查是否绕过 <Image /> 原图直出,或 Fonts API 未启用。

2.3 看引用:资源路径是否自洽

确认 CSS、JS、图片的引用路径在 preview 下全部可访问(DevTools 无 404)。若站点要部署在域名子路径(如 https://example.com/docs/),则必须配置 base(见第 5 站),否则所有资源路径都会指向错误的根路径。

3. 第 3 站,选出版社:适配器与运行模式

书的发行渠道由出版社决定;网站的”运行方式”由**适配器(adapter)**决定。适配器把 Astro 的产物接入特定平台的运行环境(Node 服务器、Serverless 函数、边缘 Worker)。

3.1 三种运行模式(注意 5.x 之后的最新变化)

模式配置渲染时机适用场景
静态(默认)output: 'static'构建期一次性生成博客、文档站、营销页(绝大多数内容站)
服务端按需渲染output: 'server' + 适配器每次请求登录态、个性化、实时数据
静态为主 + 个别动态页output: 'server' + 页面内 export const prerender = true混合大部分静态 + 少量动态(如登录页)

需要特别说明:Astro 5 起移除了独立的 output: 'hybrid' 模式,简化为——静态模式全站静态;服务端模式默认按需渲染,个别页面用 export const prerender = true 标记为预渲染。判断一个页面是否预渲染,只需看这一行标记:

astro
---
// src/pages/profile.astro(server 模式下)
// 默认按需渲染;加上这一行则改回构建期预渲染
export const prerender = true
---

3.2 安装适配器

bash
# 官方适配器:Node.js、Netlify、Vercel、Cloudflare(另有 Deno、Bun 等)
npx astro add node
npx astro add netlify
npx astro add vercel
npx astro add cloudflare

astro add 会自动安装适配器、写入 astro.config.mjs 并提示后续步骤。以 Cloudflare 为例:

js
// astro.config.mjs
import { defineConfig } from 'astro/config'
import cloudflare from '@astrojs/cloudflare'

export default defineConfig({
  output: 'server',
  adapter: cloudflare(),
})

3.3 本地开发与生产一致(Astro 6+ 的红利)

Astro 6 重构了开发服务器:通过 Vite 的 Environment API,开发环境可以直接运行目标平台的真实运行时。例如 Cloudflare 适配器在开发、预渲染、生产全程运行 workerd 运行时,本地就能直接使用 KV、D1、R2 等绑定,“本地好好的,上线就挂”这类问题从根上减少。

3.4 动态路由的两种命运

同一个 [id].astro 动态路由,在两种模式下走向完全不同:

astro
---
// src/pages/product/[id].astro

// 静态模式:构建期必须通过 getStaticPaths() 枚举所有 id,输出全部页面
// 服务端模式:无需枚举,Astro.params 在请求时按 URL 匹配
const { id } = Astro.params
const product = await fetchProduct(id)  // 静态:构建期执行;服务端:请求期执行
---

<h1>{product.name}</h1>

理解这条差异,是排查”动态页面为什么是空的 / 为什么构建报错没有生成页面”的关键。

4. 第 4 站,决定卖给谁:SSR 与静态的取舍

选出版社之后要决定”读者是谁、书放哪个书架”。这一站给你一张决策表,避免为了”用上新功能”而给纯内容站强行上 SSR:

需求特征推荐模式理由
内容几乎不变(博客、文档、营销页)静态零服务器成本、任意静态托管、响应最快、CDN 天然缓存
每个用户内容不同(登录后个人中心)按需渲染(SSR)需要请求级数据与 Cookie 能力
数据偶尔变但不至于每次渲染(文章列表、价格页)静态或 SSR + 路由缓存“最快的构建是不发生的构建”
大部分静态、少部分动态server + prerender = true 标记个别页兼得两者优点

注意 site 配置在两种模式下都重要:

js
// astro.config.mjs
export default defineConfig({
  output: 'static',
  // 站点正式域名:用于生成规范链接、sitemap 与 Open Graph 绝对地址
  site: 'https://docs.example.com',
  build: { format: 'directory' },  // 页面输出为 /about/index.html(默认)
})

4.1 错误选型的后果对照表

选型错误不报错,只产生「长期账单」。把四种典型错配的后果写清楚,决策时对照:

错误选型短期现象长期账单
纯内容站强行上 SSR多花适配器与服务器配置功夫每个访客都触发渲染,服务器成本随流量线性上涨;CDN 缓存命中率近乎为零——静态站本可以零成本扛住
个性化站点用了纯静态登录态、用户数据无处安放被迫在客户端堆 API 请求与状态管理,每个访客下载一段 JS 只为拉自己的昵称(这类局部个性化正确解是 Server Islands 或按需渲染)
server 模式但忘配缓存页面能用高读低写的文档页每次请求都全量渲染,流量上涨后源站先崩——routeRules 是免费的性能(第 5 站)
静态站误装了不需要的适配器构建产物多出运行时文件部署目标要求「纯静态文件」时直接部署失败;团队还被误导以为需要服务器

判读方法:先问「内容对谁一样」(全一样 -> 静态;按人不同 -> 按需渲染或岛屿),再问「变的频率」(几乎不变 -> 预渲染 + 缓存),两个答案组合出运行模式,而不是从「想用的特性」出发倒推。

5. 沿途的快速通道:路由缓存

“最快的构建是不发生的构建”——对按需渲染站点来说,最快的渲染是不渲染:命中缓存直接返回。Astro 7 中路由缓存(Route Caching)已稳定,通过顶层的 cache 与 routeRules 声明式配置:

js
// astro.config.mjs(server 模式项目)
import { defineConfig, memoryCache } from 'astro/config'
import node from '@astrojs/node'

export default defineConfig({
  output: 'server',
  adapter: node({ mode: 'standalone' }),  // 路由缓存只对按需渲染页面有意义
  cache: { provider: memoryCache() },     // 内置内存缓存提供方
  // routeRules 是"URL 模式 -> 缓存选项"的对象;模式支持静态路径、
  // [id] 动态段与 [...path] Rest 段,不支持 /a/** 这类 glob 通配
  routeRules: {
    '/blog/[...path]': {
      maxAge: 60 * 60,     // 缓存 1 小时(对应 Cache-Control: max-age)
      swr: 60 * 60 * 24,   // 过期后 24 小时内仍可服务旧内容(stale-while-revalidate)
    },
  },
})

缓存语义遵循标准 HTTP 缓存:maxAge 对应 Cache-Control: max-age,swr 对应 stale-while-revalidate,tags 数组可用于定向失效。缓存命中时源站根本不渲染,高读低写的页面(文档、列表)收益最大。

5.1 更进一步的 CDN 缓存提供方(实验特性)

若部署在 Netlify / Vercel / Cloudflare,可把缓存配置下发到平台边缘。三家官方适配器都提供实验性的缓存 provider,用法是把 cache.provider 从 memoryCache() 换成平台专属实现(当前需手动启用,官方计划后续版本自动接入):

js
// astro.config.mjs(Cloudflare 为例;Netlify/Vercel 同理,从各自适配器包导入)
import { defineConfig } from 'astro/config'
import cacheCloudflare from '@astrojs/cloudflare/cache'

export default defineConfig({
  output: 'server',
  cache: { provider: cacheCloudflare() },
  routeRules: {
    '/docs/[...path]': { maxAge: 3600, tags: ['docs'] },
  },
})

启用后,routeRules 的缓存指令会映射到平台原生缓存头(如 Cloudflare-CDN-Cache-Control)与 Cache-Tag 失效机制,响应缓存在全球边缘节点就近返回,进一步降低源站压力。

6. 第 5 站,物流车队:CI/CD 与平台部署

书的物流是车队,网站的物流是 CI/CD——代码一推,自动构建、自动测试、自动上线。

6.1 手工部署静态站

静态站点的产物是纯文件,可以部署到任何静态托管:

bash
npm run build
# 把 dist/ 上传到任意平台:
#   GitHub Pages / 阿里云 OSS / S3 / Nginx 等

子路径部署时配置 base:

js
// astro.config.mjs
export default defineConfig({
  base: '/docs/',  // 部署在 https://example.com/docs/ 时必配
})

6.2 平台一键部署(Netlify / Vercel)

Netlify 与 Vercel 对 Astro 提供第一方支持:在控制台导入 Git 仓库,平台自动识别 Astro 并填入默认参数(构建命令 npm run build、输出目录 dist)。静态与按需渲染均开箱即用,且支持每次提交的预览部署与一键回滚。

6.3 Cloudflare Pages / Workers

bash
npm run build
# 纯静态:直接上传产物
npx wrangler pages deploy dist
# 使用 @astrojs/cloudflare 适配器的 SSR 站点:部署为 Workers
npx wrangler deploy

Astro 6+ 与 Cloudflare 深度合作(官方适配器在开发期即运行 workerd),KV/D1/R2 绑定在本地即可使用,上线后行为一致。

6.4 用 GitHub Actions 搭一条完整流水线

yaml
# .github/workflows/deploy.yml(示意)
name: build-and-deploy
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22          # Astro 7 要求 Node 22+
      - run: npm ci                 # 按锁文件精确安装依赖
      - run: npm run build          # 生产构建
      - run: npx astro check        # 类型检查作为质量门禁,失败即阻断
      # 之后可按需上传 dist/ 到 OSS / S3 / 触发平台部署钩子

流水线的价值在于门禁:安装、构建、类型检查任一环节失败,部署自动阻断,把错误挡在上线之前。FANDEX 的 CI 还包含链接检查与构建统计,保证 2000+ 篇文档构建稳定、无死链。

6.5 真实案例:FANDEX 的 GitHub Pages 流水线

本仓库(FANDEX)的 .github/workflows/deploy.yml 就是一条值得逐段读的生产流水线——内容站 + GitHub Pages 形态的完整样本:

yaml
# 关键步骤节选(完整文件见 .github/workflows/deploy.yml)
on:
  push:
    branches: [main]
    paths: ['app-web/**', 'cnt-content/**', 'pnpm-lock.yaml', '...']  # 触发路径过滤
  pull_request:
    branches: [main]                                                    # PR 也跑构建验证
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}        # 旧构建自动取消
jobs:
  build:
    steps:
      - run: pnpm install --frozen-lockfile      # 锁文件精确安装
      - run: pnpm -C app-web audit --prod --audit-level high   # 依赖安全门禁
      - run: pnpm --filter @fandex/web sync      # 内容元数据补全(管线第 1 步)
      - run: node scripts/content-audit.mjs      # 内容审计门禁(HIGH 级问题阻断)
      - run: pnpm typecheck && pnpm --filter @fandex/web lint
      - run: pnpm build:web                      # Astro 生产构建
      - run: pnpm --filter @fandex/web qa        # 构建后 QA 检查
      - uses: actions/upload-pages-artifact@v5
        with: { path: app-web/dist }
  deploy:
    needs: build
    if: github.event_name == 'push'              # 只有 push 到 main 才真正部署
    uses: actions/deploy-pages@v5

六个值得抄走的设计:

  1. paths 过滤触发:内容站的大部分提交只动文档或只动前端,过滤路径避免无关提交空烧构建分钟数;
  2. PR 构建但不部署:build job 对 PR 也执行(改坏了主分支构建立刻红),deploy job 用 if: github.event_name == 'push' 卡住——「验证免费、发布要过合并关」;
  3. concurrency 取消旧构建:连续推送时只构建最新提交,PR 场景取消旧任务省资源;
  4. 审计前置:依赖安全审计与内容审计都放在 build 之前——最便宜的失败位置是最早的失败位置;
  5. sync 在 build 前:内容管线(frontmatter 补全、模块注册)是构建的输入前置步骤,顺序错乱是内容站流水线最常见的翻车点;
  6. 构建产物走 artifact:upload-pages-artifact 把 dist 作为产物在 job 间传递,部署 job 只认产物不重跑构建——两个 job 职责分离。

7. 第 6 站,挂牌营业:域名、HTTPS 与收尾检查

7.1 域名与 HTTPS

  • 自定义域名:在托管平台把域名解析(CNAME/ALIAS)指向站点,并配置平台侧的自定义域;
  • HTTPS 证书:Netlify、Vercel、Cloudflare Pages 都提供免费自动证书(Let’s Encrypt 类),配置域名后自动签发与续期,无需手动管理;
  • 环境变量:process.env / import.meta.env 中前缀为 PUBLIC_ 的变量才会暴露到浏览器端,密钥类变量只能用于服务端代码。SSR 站点的密钥(数据库口令等)务必只存平台环境变量,绝不提交到仓库。

7.2 上线前检查清单

第一,site 与 base 配置正确,sitemap 与规范链接生成无误;

第二,SSR 站点确认适配器与平台运行时匹配(Node 函数 / 边缘 Worker / Serverless 函数);

第三,动态页面配置 routeRules 缓存策略,上线后观察缓存命中率;

第四,构建产物无意外大 JS 包、无 404 资源(按第 2 站质检流程过一遍);

第五,上线后手动走一遍核心路径:首页、一篇内容页、一个交互岛屿、一个 404 页。

动手实践

任务:给一个静态 Astro 站点跑通「本地构建 -> 产物质检 -> 流水线」全链路。

  1. 本地跑 npm run build,按第 2 站流程清点 dist/:页面数与 src/pages 对账、_astro/ 里资源的哈希命名、有无意外大文件;
  2. 制造「错误选型」体验:给纯静态站临时 npx astro add node 并切 output: 'server',构建对比产物形态(出现服务器入口文件),再切回 static 观察产物恢复——用产物差异理解两种模式的物理区别;
  3. 把 6.4 节的 GitHub Actions 示意流水线搬进自己的项目(GitHub 托管时),把 run: 换成你的构建命令,观察首次运行;故意提交一个 frontmatter 错误,验证门禁红,修复后绿灯;
  4. 对照 6.5 节读本仓库的 deploy.yml 全文,找出文中六个设计点在你流水线里的对应物(或缺席项)。
参考现象(先自己试,再展开对照)

第 2 题:server 模式的 dist 里出现 entry.mjs(Node 适配器)之类的服务器入口与 host manifest,页面 HTML 不再全量预生成;切回 static 后产物恢复为纯 HTML 目录。物理形态的差异是两种模式所有行为差异的根源。

第 3 题:门禁红的位置在内容审计或构建步骤(取决于错误类型),PR 页面给出失败原因——「错误挡在合并前」的体感。注意流水线的价值密度取决于门禁的严格程度:全部 continue-on-error 的流水线只是个构建计时器。

第 4 题提示:paths 过滤、PR 构建不部署、concurrency、审计前置、sync 前置、artifact 传递——逐项对照后,缺席项就是你自己流水线的下一个改进点。

8. 常见错误与对策

常见错误典型报错/现象原因解决办法
部署后样式/图片全部 404页面 HTML 正常但资源路径错站点部署在子路径,未配置 baseastro.config.mjs 中设置 base: '/子路径/' 后重新构建
动态路由构建报”没有生成页面”报错提示 getStaticPaths() 返回空静态模式下动态路由必须枚举所有路径修正 getStaticPaths() 数据源;或改用 server 模式按请求渲染
部署到平台后是空白页平台显示 404 / 无法访问未安装适配器,或输出目录配置不对按平台装对应适配器;确认构建输出目录为 dist
SSR 站点本地正常、线上异常生产环境报运行时错误平台运行时与本地 Node 环境差异(老版本尤其常见)升级 Astro 6+ 并确认适配器在开发期即运行目标运行时;核对 Node 版本 22+
npm run build 在 CI 中失败构建命令报错退出CI 环境 Node 版本过低(Astro 7 要求 Node 22+)actions/setup-node 指定 node-version: 22
缓存策略不生效页面每次都重新渲染未配置 cache.provider,或只配了 routeRules 没开 provider配置 cache: { provider: memoryCache() }(或平台 CDN provider);同时确认 routeRules 用的是 /blog/[...path] 这类路由模式而非 /blog/** 通配

9. 一句话记忆

“构建是印刷、产物要质检、适配器定发行渠道、缓存是快速通道、CI 是物流车队、域名与 HTTPS 是最后挂牌——上线是一趟从 dist/ 到全球 CDN 的完整旅程。”