# Astro Github Pages

> 用 Astro 建一個靜態網站並上線到 GitHub Pages，附兩道 CI 守門：設計規範守門（顏色只准在 token 檔、字級一律階梯且不小於 18px、禁 !important、禁外部 CDN）與中文去 AI 味守門（兩級判定，強 AI 指紋單一命中即擋，軟訊號跨三層才升級）。含 IndexNow 即時收錄、sitemap 提交、部署後逐網址驗 200。Use when the user asks to 用 Astro 開新網站、建 GitHub Pages 網站、或要為既有 Astro 專案加設計規範／文案品質的 CI 守門。

- Skill: `yao-care/astro-github-pages` (Agent Skill, multi-file: 10 files)
- Install (CLI): `npx skillmds@latest add yao-care/astro-github-pages`
- Raw SKILL.md: https://api.skillmd.com/api/skills/yao-care/astro-github-pages/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: yao-care (https://skillmd.com/u/yao-care)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/yao-care/astro-github-pages

---


# astro-github-pages — Astro + GitHub Pages 建站 SOP

從零到「Pages 上線、兩道守門生效、搜尋引擎收得到」。

**不要自行發明頁面內容或資訊架構。** 骨架上線後就去問使用者要網站地圖與發展方向。這條是刻意的：AI 自己編出來的頁面結構通常對不上真實業務。

模板檔在本 skill 目錄的 `templates/`。

## 為什麼要有守門

兩個問題只有在 CI 擋下來才會真的被解決：

1. **設計會漂。** 一個人寫 `#3a7`、一個人寫 `rgb(51,119,85)`、下一個人寫 `var(--color-primary)`，三個月後沒人知道哪個是對的。字級同理，`font-size: 14px` 一旦混進來就會繁殖。
2. **AI 寫的中文有指紋。** 「不僅…更」「值得注意的是」「隨著…的發展」「這不是 X，而是 Y」。單獨看每一句都通順，整篇讀起來就是機器味。人工複核會累、會鬆，正則不會。

所以 `build` 一定串這兩支腳本，CI fail 就不上線。

## 守門規則

### 設計守門 `scripts/check-design.mjs`

掃 `src/` 下所有 `.css/.astro/.svelte`，違規 exit 1：

1. 禁 px 字級。一律 `var(--text-*)` 階梯，最小 18px，內文不小於 `--text-base`。
2. 顏色只准出現在 `src/styles/variables.css`。oklch 為準、hex 作 fallback。
3. 禁 `!important`。
4. 禁外部 CDN（fonts.googleapis、gstatic、cdnjs、unpkg、jsdelivr）。字型用系統堆疊。
5. **css 檔白名單**：`src/` 下的 `.css` 只准 `styles/variables.css` 與 `styles/global.css`。新增 css 檔即 fail，元件樣式寫 scoped `<style>`。
6. **階梯下限**：`--text-*` 的 token 值本身一律不小於 18px（1.125rem），`clamp()` 以最小值計。少了這條，前面第 1 條會被「把 token 值調小」繞過。

### 去 AI 味守門 `scripts/check-content.mjs`

掃 `src/**/*.md(x)`，兩級判定：

- **ERROR（單一命中即擋）**：誤判率接近零的強 AI 指紋。「不是 X 而是 Y」、「不僅…更」、「值得注意的是」、「換句話說」式的空泛收束、「隨著…的發展」開場、模糊引用（研究顯示／專家認為）、模板化的第一人稱開場。
- **WARN（軟訊號）**：分詞彙、句式、結構、語氣四層。破折號、「這是一個 X，也是一個 Y」、拔高收尾、賦能／助力這類詞。單一命中不擋；**同一檔跨三個以上不同層級才升級成 ERROR**。

那條「跨三層才算」是重點。單一特徵人類也會用，只憑一個就擋會產生大量誤判，接著團隊就會開始忽略守門，守門就死了。

**掃描範圍刻意只掃變動檔**（相對 `origin/main`）。抓不到 git base 時掃 0 個檔並 exit 0，永不誤擋。這樣既有內容不必先全部改乾淨才能用，新內容從今天開始守住。另有 `pnpm check:content:all` 做全站普查，恆 exit 0，只報告不擋。

**逐字轉錄豁免**：frontmatter 標 `sourceVerbatim: true` 的檔整檔跳過。用途是「把既有網站原文一字不改搬過來」，原文出現「不僅…更」是原作者寫的，改寫它等於竄改別人的文案。**只准用於逐字轉錄**；新寫的文案掛這個旗標等於自廢守門，驗收時要逐檔比對旗標檔是否真的出自來源。

站台特化規則不進通用模板，各站在腳本內的擴充點自行增補。

## Phase 0. 收集輸入（缺才問，一次問完）

| 輸入 | 說明 | 預設 |
|---|---|---|
| repo 名稱 | 通常等於網域 | 必問 |
| GitHub org 或帳號 | | 必問 |
| 網址模式 | A=自訂網域（需 CNAME 加使用者設 DNS）／B=`https://<org>.github.io/<repo>/`（含 base 子路徑） | 問 |
| 品牌主色 | 可後補，沒給就用模板佔位色並在回報裡註明待定 | 佔位色 |

## Phase 1. 專案骨架

不用 `create astro` 互動精靈，直接手刻，確定性高。

```
<repo>/
├── package.json
├── astro.config.mjs
├── .gitignore                     # node_modules/ dist/ .astro/
├── scripts/
│   ├── check-design.mjs           # 複製 templates/，原樣
│   ├── check-content.mjs          # 複製 templates/，原樣
│   ├── indexnow-submit.mjs        # 複製 templates/，原樣
│   └── gsc-submit-sitemap.mjs     # Phase 4 手動跑一次
├── public/
│   ├── CNAME                      # 只有模式 A，內容＝網域
│   ├── robots.txt                 # 複製 templates/，{{SITE_URL}} 換正式網址
│   ├── <indexnow-key>.txt         # 檔名＝金鑰、內容＝同一把金鑰
│   └── favicon.svg
└── src/
    ├── styles/variables.css       # 複製 templates/，改品牌色（oklch 與 hex 兩處都改）
    ├── styles/global.css          # 複製 templates/
    ├── layouts/BaseLayout.astro   # 複製 templates/
    └── pages/index.astro          # 極簡佔位頁
```

IndexNow 金鑰，每站獨立產一把：

```bash
KEY=$(openssl rand -hex 16)
printf '%s' "$KEY" > public/$KEY.txt   # IndexNow 的驗證機制：檔名與內容都是金鑰
echo "INDEXNOW_KEY = $KEY"             # 填進 deploy.yml 的 {{INDEXNOW_KEY}}
```

這把金鑰不是機密（本來就公開託管在 `/<key>.txt`），直接寫進 workflow env，不必進 secrets。

`package.json` 要點（版本以 `pnpm add` 實際解析為準，不要寫死過時版號）：

```json
{
  "type": "module",
  "engines": { "node": ">=22.12.0" },
  "scripts": {
    "dev": "astro dev",
    "build": "node scripts/check-design.mjs && node scripts/check-content.mjs && astro build",
    "check:design": "node scripts/check-design.mjs",
    "check:content": "node scripts/check-content.mjs",
    "check:content:all": "node scripts/check-content.mjs --all"
  },
  "dependencies": { "astro": "^6", "@astrojs/sitemap": "^3" }
}
```

**`build` 必須串兩支守門腳本。** 這是整套的施力點，拆掉就沒有強制力了。

`astro.config.mjs`：

```js
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';

export default defineConfig({
  site: 'https://<正式網址>',   // 模式 B 填 'https://<org>.github.io'
  // base: '/<repo>',           // 只有模式 B 需要
  output: 'static',
  build: { format: 'directory' },
  // 模式 B 必加 filter：sitemap 會多產一筆「site 加 base、結尾無斜線」的網址，
  // GitHub Pages 對它回 301，部署後驗 200 的 job 會誤判失敗。
  integrations: [sitemap({ filter: (page) => page.endsWith('/') })],
});
```

## 模式 B 的三個坑

實作時踩過，寫下來省下重踩的時間：

1. 有 `base` 時站內連結一律經 `import.meta.env.BASE_URL` 組，別硬編 `/xxx`。這是 project pages 最常見的斷鏈原因。`BaseLayout.astro` 裡 sitemap 的 `<link>` 也要跟著改。
2. `SITE_URL` 會帶子路徑，IndexNow 腳本要補尾斜線並改用相對的 `keyLocation`。
3. **GSC 不能用 `sc-domain:`**。`github.io` 的 DNS 不在你手上，只能建「網址前置字元」資源 `https://<org>.github.io/<repo>/`，用 HTML 檔或 GA4 代碼驗證。

## Phase 2. 本地驗證

```bash
pnpm install && pnpm build
```

build 要先印出設計守門通過的訊息才算生效。

如果為了截圖或量測起了 `pnpm preview` 或 dev server，**收尾一定要 kill**。Astro 遇到 port 被佔會自動換 port，所以洩漏的行程不會報錯，只會默默累積。

## Phase 3. 建 repo、開 Pages、推上線

```bash
git init -b main && git add -A && git commit -m "chore: scaffold astro site with design gate"
gh repo create <org>/<repo> --public --source . --remote origin
gh api -X POST repos/<org>/<repo>/pages -f build_type=workflow   # 409 代表已開啟，可忽略
# 模式 A 必做：artifact 裡的 public/CNAME 不會自動設定 custom domain，
# 一定要明確 PUT 一次，否則站只在 <org>.github.io 生效。
gh api -X PUT repos/<org>/<repo>/pages -f cname=<網域>
mkdir -p .github/workflows   # 複製 templates/deploy.yml，換掉 {{SITE_URL}} 與 {{INDEXNOW_KEY}}
git add -A && git commit -m "ci: github pages deploy workflow" && git push -u origin main
```

順序刻意是「先開 Pages 再 push」，免得第一次 workflow 因為 Pages 還沒啟用而 fail。真的 fail 了就開啟後 `gh run rerun <id>`。

deploy.yml 的 job 鏈：build（含守門）→ deploy → verify（逐網址驗 200）→ indexnow（把 sitemap 網址送 Bing/Yandex/Seznam/Naver）→ notify-failure。

**Google 不吃 IndexNow。** Google 靠 `robots.txt` 裡的 `Sitemap:` 行自動發現，另外在 Phase 4 對 GSC 提交一次。

**驗證上線，沒打到 200 不准回報完成：**

```bash
gh run watch --repo <org>/<repo> --exit-status
curl -s -o /dev/null -w '%{http_code}' <正式網址>/
```

模式 A 的 DNS 在使用者手上。先驗 workflow 全綠，然後回報要設的記錄（`CNAME → <org>.github.io`，apex 用 A 記錄指 185.199.108.153–185.199.111.153），並明講「自訂網域等 DNS 生效後再驗」。**不可宣稱網域已經通。**

上線後就去要網站地圖與發展方向。

## Phase 4. 搜尋引擎收錄

向 GSC 提交 sitemap 一次。`robots.txt` 的 `Sitemap:` 行會被動發現，主動提交比較快。

```bash
pnpm add -D google-auth-library
GSC_SA_KEY=<服務帳號金鑰路徑> GSC_SITE='sc-domain:example.com' \
SITE_URL='https://<正式網址>' node scripts/gsc-submit-sitemap.mjs
```

不想裝套件就在 GSC 網頁介面的 Sitemaps 手動提交 `sitemap-index.xml` 一次，等價。

GA4 與 GSC 的後台操作都在使用者端，**只引導、不代辦、不臆測已完成**。

## 收尾

1. repo 建一份精簡 `CLAUDE.md`：技術棧、設計規範六條、常用指令、CI 各 job 說明。
2. 回報：repo 網址、上線網址（附實測 HTTP 狀態碼）、品牌色是否還是佔位、搜尋引擎提交狀態。

## 這個 skill 不做的事

- 不裝多餘的 integration（svelte、mdx、pagefind 等到有需求再加）。
- 不代辦 Google 或第三方後台操作。
- 不碰非 GitHub Pages 的部署方式。

