label: "目的地"
folder: "src/content/destinations"
create: true; extension: "md"; format: "frontmatter"
fields:
- { name: "title", label: "標題", widget: "string" }
- { name: "image", label: "封面圖", widget: "image", required: false }
- { name: "location", label: "地點", widget: "string" }
- { name: "description", label: "簡介", widget: "text" }
- { name: "highlights", label: "特色項目", widget: "list" }
- { name: "color", label: "主題色", widget: "select",
options: [{ label: "🟠 暖橘", value: "#E27D60" }, ...] }
- { name: "bestSeason", label: "適合季節", widget: "string" }
- { name: "avgCost", label: "平均花費", widget: "string" }
- { name: "featured", label: "顯示於首頁", widget: "boolean" }
- { name: "body", label: "詳細介紹", widget: "markdown" }
-
name: “gallery” label: “攝影集” folder: “src/content/gallery” create: true; extension: “md”; format: “frontmatter” fields:
- { name: “title”, label: “標題”, widget: “string” }
- { name: “image”, label: “照片”, widget: “image”, required: false }
- { name: “description”, label: “照片描述”, widget: “text” }
- { name: “category”, label: “分類標籤”, widget: “string”, hint: “夜拍、古都、沙漠、街拍…” }
- { name: “location”, label: “拍攝地點”, widget: “string” }
- { name: “device”, label: “拍攝裝置”, widget: “string” }
- { name: “order”, label: “排序”, widget: “number”, value_type: “int”, default: 99 }
-
name: “about” label: “關於我” folder: “src/content/about” create: false; delete: false; extension: “md” fields:
- { name: “name”, label: “姓名”, widget: “string” }
- { name: “avatar”, label: “頭像照片”, widget: “image”, required: false }
- { name: “tagline”, label: “個人標語”, widget: “string” }
- { name: “bio”, label: “簡介”, widget: “text” }
- { name: “instagram”, label: “Instagram”, widget: “string” }
- { name: “twitter”, label: “Twitter/X”, widget: “string” }
- { name: “body”, label: “詳細介紹”, widget: “markdown” }
### 6.4 重要設計原則 {#cms-principles}
| 原則 | 說明 |
|------|------|
| select → string | 手機上原生 `<select>` 會跳掉,改用 string + hint |
| create: false | 關於我只有一筆,禁止新增 |
| value_type: "int" | Sveltia 用 snake_case |
| { label, value } 格式 | select options 統一格式,手機才正常 |
| YAML 2 空格縮排 | fields 在 collection 下縮 4 格 |
| **上傳自動壓縮** | `public/admin/index.html` 內建 JS:攔截 >1MB 圖片,瀏覽器端自動縮到 1920px,客戶無需任何操作 |
| **Token 優先 OAuth** | 預設用 Token 登入後台(零 API = Pages 部署 = 更穩更快)。除非客戶要求 GitHub 一鍵登入,才走 OAuth(§2 C.1 三模式對比) |
> [[#table-of-contents|← 回目錄]]
---
## 第 7 章:問題解決方案 {#troubleshooting}
> **👤 你:描述觀測結果(精確!)** — **🤖 AI:照檢查清單抓錯、修程式碼**
>
> AI 不是通靈。你給的線索越精確,AI 修得越快。照 7.17 的優先順序查,不要跳步。
### 7.1 Astro script 機制陷阱 {#trouble-astro-script}
| 寫法 | Astro 行為 | onclick 能找到? |
|------|-----------|:--:|
| `<script>` | Hoist 到 head,打包成 module | ❌ |
| `<script is:inline>` | 原封保留 | ✅ 但 `{var}` 不處理 |
| `<script define:vars>` | 保留原位,包 module | ❌ function 是 module-local |
**解法:** `const fn = window.fn = function() {}` — 雙重綁定。
### 7.2 Lightbox 開發史(8 次失敗) {#trouble-lightbox}
| # | 方法 | 失敗原因 |
|:--:|------|------|
| 1 | DOMContentLoaded + querySelector | Script 在 Layout 外,被 Astro 吃掉 |
| 2 | onclick + 移到 Layout 內 | Script 被 hoist |
| 3 | is:inline | `{galleryData}` 變純文字 |
| 4 | define:vars | function module-local |
| 5 | JSON data tag + is:inline | 沒被 Astro 處理 |
| 6 | window.openLightbox | 成功!但 scroll lock 有問題 |
| 7 | touch-action: none | 手機卡住 |
| 8 | **`<dialog>`** | ✅ 一次完美 |
**結論:先找瀏覽器原生 API,不要自己手刻。** 詳見 [[#section-gallery|§5.7 攝影集]]。
### 7.3 手機分類選單跳掉 {#trouble-mobile-select}
改用 `string` widget + `hint`:
```yaml
# ❌ 手機會跳掉
- { widget: "select", options: ["夜拍", ...] }
# ✅ 手機正常
- { widget: "string", hint: "夜拍、古都、沙漠..." }
7.4 Footer 部署平台標示
grep -rn "GitHub Pages\|github pages" src/ public/
7.5 YAML 縮排錯誤
collections:
- name: "blog"
label: "旅遊日誌"
fields: # 4 格
- { name: "title", ... } # 6 格
7.6 DNS 不生效
dig 你的網域 +short
curl -I https://你的網域
Proxy 狀態:橘雲 vs 灰雲(SSL 相關)。
7.7 垃圾桶問題速查
| 症狀 | 解法 |
|---|---|
| 後台進不去 | YAML 縮排檢查 |
| valueType 報錯 | 改 value_type(snake_case) |
| 照片上傳後消失 | 等 2-3 分鐘 Actions 轉完 |
| 封面圖靠左不滿版 | padding 移到內層 |
| ClientRouter 跳動 | 移除 ClientRouter |
| Footer 佔太多空間 | mt-32 py-12 → mt-16 py-8,間距減半 |
| 按鈕連結 404 | 頁面刪除/改名後,檢查所有 href="/old-path" 是否還有效 |
7.8 Footer 空間比例修正
Footer 預設的 mt-32 py-12 會讓底部區塊跟內容區域之間有 128px 空白,佔太多版面。
統一修正:
Footer class: mt-32 → mt-16(-50% 外距)
py-12 → py-8(-33% 內距)
主列間距: mb-8 → mb-4(-50%)
底部區間距: pt-6 → pt-3(-50%)
改完後 Footer 佔的視覺比例減少約 40%。
暗色模式 <hr> 分隔線
ming-website 踩坑:白色分隔線在暗色模式下像雷射筆,極度刺眼。
<!-- ❌ 預設(暗色背景上太亮) -->
<hr />
<!-- ✅ 修正(暗色下低調) -->
<hr class="border-gray-300 dark:border-gray-700" />
原則:所有 <hr> 都要加 dark:border-gray-700。 兩站都適用。
7.9 CMS schema 同步檢查
CMS config.yml 和 content.config.ts 的 collection 定義必須同步:
| 問題 | 檢查方式 |
|---|---|
| CMS 有 collection 但 Astro 沒 schema | 資料寫入但前端抓不到 → 無聲失敗 |
| Astro 有 schema 但 CMS 沒 collection | 前端能顯示但客戶無法編輯 |
| 欄位名稱不一致 | config.yml 用 buttonText 但 schema 用 cta → 值存不進去 |
每次改 CMS config 後:
# 確認兩邊的 collection 名稱一致
grep "^ - name:" public/admin/config.yml
grep "'[a-z]*':" src/content.config.ts
7.10 git remote 確認
新專案常見問題:commit 推到錯誤的 GitHub 帳號。
每次開新專案後:
git remote -v
# 確認輸出指向正確的 repo(如 minglabtw/ming-website)
# 不是舊專案的帳號(gimmi520/ming-website)
config.yml 的 repo: 名稱陷阱
ming-website 踩坑:config.yml 寫 repo: gimmi520/minglab-website,但某次誤以為是 ming-website。名稱差一個單字 → CMS 無法存檔 → 客戶登入後台看到「儲存失敗」。
# config.yml(Sveltia CMS 靠這行知道要推到哪個 GitHub repo)
backend:
repo: gimmi520/minglab-website # ← 必須跟 GitHub 上拼法完全一致
檢查指令:
# 確認 config.yml 的 repo 跟 git remote 一致
grep "repo:" public/admin/config.yml
git remote -v
# 兩邊的「帳號/專案名」必須完全相同
改 config.yml 後要重新部署,不只改本地檔案。
7.11 部署後網站打不開
如果 Worker 部署成功但域名打不開,先檢查 DNS:
| 檢查 | 指令 / 位置 |
|---|---|
| DNS 有記錄嗎? | dig 你的網域 +short |
根域名(如 minglab.tw) | Cloudflare DNS → 加一條 A record @ → Proxy 開(橘雲) |
子域名(如 travel.xxx.tw) | Cloudflare DNS → 加一條 CNAME → Proxy 開 |
真實案例: ming-website 部署後 minglab.tw 打不開,根因是 DNS 沒有 A 記錄,流量根本沒進 Cloudflare。
7.12 OAuth 登入後空白畫面
| 檢查 | 說明 |
|---|---|
Routes api/* 指向正確的 OAuth Worker? | 不要指到別的專案的 OAuth Worker |
OAuth Worker 的 redirect_uri 有指定嗎? | 加 redirect_uri: \https://${hostname}/api/callback`` 確保 callback 回到正確網域 |
| GitHub OAuth App 的 callback URL 只有一個? | 多個 callback URL 時 GitHub 可能選錯,建議獨立 OAuth App |
真實案例: minglab.tw/api/* Route 指到 ming-travel-oauth(旅站的),callback 跑到 travel.minglab.tw,後台永遠收不到 token。
7.13 後台登入後「沒有權限」
public/admin/config.yml 的 repo 欄位名稱不對。
# 確認 repo 名稱跟 GitHub 一致
backend:
repo: gimmi520/正確的repo名
真實案例: repo 改名為 minglab-website 後忘記更新 config.yml,CMS 嘗試存取已刪除的舊 repo。
7.14 部署一直被誤判為 Workers
Cloudflare 會記住 repo 的部署類型。如果 repo 曾連過 Workers,重新建立 Pages 也會被強制用 Workers 模式。
| 解法 | 說明 |
|---|---|
| 開全新 repo(推薦) | 零歷史記錄,Cloudflare 正確判為 Pages |
| 不要在同一個 repo 上反覆重建 | 每次重建 Cloudflare 只會回到之前記住的模式 |
真實案例: ming-website repo 曾連過 Workers → 刪掉 → 新建 Pages → 仍被強制用 Workers。改為全新 repo minglab-website 後一次成功。
7.15 KV Namespace 衝突
@astrojs/cloudflare adapter 會自動建立 KV Namespace。如果刪除專案重建,舊 KV 殘留會報 already exists 錯誤。
| 解法 | 說明 |
|---|---|
| Cloudflare → Storage → KV → 刪除舊的 Namespace | 解綁後才能刪 |
| 先解綁 Worker 的 KV binding → 再刪 KV | Settings → Bindings → 移除 SESSION |
7.16 頁面標題沒更新
Layout.astro 改了預設 title,但某些頁面 <Layout title="舊標題"> 硬編碼覆蓋了預設值。
| 檢查 | 指令 |
|---|---|
| 所有頁面的 title prop | grep -rn "Layout title=" src/pages/ --include="*.astro" |
| 預設 title | 在 Layout.astro 的 title = '...' |
真實案例: Layout.astro 改了「銘誠科動」但 index.astro 硬編碼「銘於心」,部署後標題一直沒變。