---
name: vite-tunnel
description: 在 Vite dev server 前掛 Cloudflare Named Tunnel，給固定 HTTPS hostname。Use when 跨裝置（手機 / 平板 / 另一台筆電）測 OAuth flow、跨裝置測 webhook callback、行動裝置 debug 需要穩定 HTTPS URL、Vite 加 Cloudflare tunnel、設定 dev HTTPS hostname、改 OAuth callback 不想每次回 provider 後台。對 Workers/Pages 用官方 @cloudflare/vite-plugin 內建 tunnel；對一般 Vite 走第三方 vite-plugin-cloudflare-tunnel。不適用於 prod tunnel（這 skill 只管 dev）、其他 provider（ngrok / localtunnel）、單機開發夠用的情境（用 vendor/snippets/dev-auth/ cookbook 繞 OAuth 更快）、需要 quick tunnel 隨機 hostname 的場景（直接跑 cloudflared tunnel --url 即可，不必走這 skill）。
effort: medium
---
<!-- 🔒 LOCKED — managed by clade · auto-generated by sync-to-agents; edit source in .claude/ then re-run sync -->

對應 cookbook：`~/offline/clade/vendor/snippets/vite-tunnel/`

## 何時用 vs 何時不用

**用這個 skill**：
- 真實 OAuth provider flow 必須跑（Apple Sign In、不能 mock、要驗 production-like cookie flow）
- 手機 / 平板 / 另一台筆電要連 dev server 跑 OAuth callback / webhook
- 對外人秀 dev preview，需要穩定 hostname

**改走其他做法**：
- 單機開發 + Claude 並行驗證 → 用 `vendor/snippets/dev-auth/` cookbook 繞 OAuth（更快、不依賴外部網路）
- 短時間 webhook 測試（5 分鐘解決）→ `cloudflared tunnel --url http://localhost:5173` 拿 quick tunnel，OAuth 註冊壞掉沒關係因為時間短
- 多 worktree 並行 dev server → dev-auth + cookie namespace 比 tunnel 簡單

## Step 1: 偵測專案部署 target

讀 `package.json` / `wrangler.toml` / `wrangler.jsonc`：

| 偵測信號 | 路線 | 後續 |
| --- | --- | --- |
| 有 `wrangler.toml` / `wrangler.jsonc`，或 `@cloudflare/vite-plugin` 在 deps | **官方路線** | `@cloudflare/vite-plugin` 內建，按 `t + Enter` 拉 tunnel |
| 一般 Vite 專案（純 SPA / Vite SSR） | **第三方路線** | 裝 `vite-plugin-cloudflare-tunnel` |

```bash
# 偵測指令參考
test -f wrangler.toml || test -f wrangler.jsonc && echo "Workers project"
grep -q '"@cloudflare/vite-plugin"' package.json && echo "Workers project"
```

## Step 2: 前置檢查

跟使用者確認：

1. Domain 是否**掛在 Cloudflare DNS**（nameserver 切過去；不只 zone delegated）
2. `cloudflared` CLI 已裝（`brew install cloudflared`）
3. 已登入：`cloudflared tunnel login`（看 `~/.cloudflared/cert.pem` 存在）

任一缺失就停下來提示使用者補齊，不要硬推進。

## Step 3: 取得 API token

- **官方路線**：不需要 API token（plugin 走 `cert.pem` 認證），跳到 Step 4
- **第三方路線**：需要

從 **Notion** 找「Cloudflare DNS+Tunnel token」。沒這頁的話：
1. 跟使用者確認要新建 token
2. 提示 Cloudflare dashboard 路徑：My Profile → API Tokens → Create Token
3. Scope: `Account → Cloudflare Tunnel → Edit` + `Zone → DNS → Edit`（建議 apply to All zones 省得未來重發）
4. 拿到 token 後請使用者存進 Notion 再貼進 `.env.local`

## Step 4: 跑 setup 腳本

```bash
~/offline/clade/vendor/snippets/vite-tunnel/bin/dev-tunnel-setup.sh \
  <hostname> <tunnel-name>
```

約定 tunnel-name = `<project>-dev`（如 `perno-dev`、`tdms-dev`）。

腳本會：
1. 偵測 cloudflared / cert.pem，缺則停手指示
2. `cloudflared tunnel create <name>`（idempotent，已存在則重用）
3. `cloudflared tunnel route dns <name> <hostname>`
4. 寫 / 補 `.env.local`（不覆蓋既有值）：`TUNNEL_HOSTNAME` / `TUNNEL_NAME` / `TUNNEL_ID` / `CLOUDFLARE_API_KEY`（空值待填）

## Step 5: 填 token + patch vite.config

### 第三方路線

1. 填 token：把 Notion 拿到的字串貼進 `.env.local` 的 `CLOUDFLARE_API_KEY=`（注意是 `KEY` 不是 `TOKEN`，第三方 plugin 慣例命名）
2. 安裝 plugin：
   ```bash
   pnpm add -D vite-plugin-cloudflare-tunnel
   ```
3. 抄範本：把 `~/offline/clade/vendor/snippets/vite-tunnel/templates/vite.config.generic.snippet.ts.template` 對應段合進 `vite.config.ts`。重點兩塊：
   - `plugins: [..., cloudflareTunnel({ hostname, tunnelName, apiToken })]`
   - `server: { allowedHosts: [hostname] }`

   不要直接覆蓋使用者既有 `vite.config.ts`——只 merge 必要兩塊。

### 官方路線（Workers/Pages）

抄範本：`~/offline/clade/vendor/snippets/vite-tunnel/templates/vite.config.workers.snippet.ts.template`，重點是 `cloudflare({ tunnel: { name: '<tunnel-name>' } })`。`vite dev` 模式 plugin 會自動處理 host 驗證；`vite preview` 才要手動補 `preview.allowedHosts`。

## Step 6: OAuth provider 註冊 callback

提示使用者去 provider 後台（依專案 stack 不同）補 callback URL：

| Provider | 通常路徑 |
| --- | --- |
| Google OAuth | Google Cloud Console → APIs & Services → Credentials → OAuth client → Authorized redirect URIs |
| GitHub OAuth App | GitHub Settings → Developer settings → OAuth Apps → Authorization callback URL |
| Supabase Auth | Supabase dashboard → Authentication → URL Configuration → Redirect URLs |
| Apple Sign In | Apple Developer → Identifiers → Services IDs → Return URLs |

新加一筆 `https://<hostname>/auth/callback`（**不**動既有 localhost 那筆，dev 兩用）。

## Step 7: 啟動 + 驗證

```bash
pnpm dev
# 官方路線：按 t + Enter 拉 tunnel
# 第三方路線：plugin 自動拉，等 ready log
curl -I https://<hostname>   # 預期 200 / 301
```

驗收：
- [ ] `curl -I https://<hostname>` 通
- [ ] 桌面瀏覽器開 `https://<hostname>` 看到 dev server 頁面
- [ ] 手機 / 另一裝置開 `https://<hostname>` 跑完 OAuth flow，登入成功
- [ ] 登入後 reload 頁面 session 還在（cookie Secure 正常）

任一條沒過，去看 cookbook 的 Gotchas 表（`vendor/snippets/vite-tunnel/README.md`）。

## 共用 tunnel 模型

**每專案各建一條 tunnel**（不要把多專案 hostname route 到同一 tunnel）：
- tunnel-name 跟專案 name 綁定（`perno-dev` / `tdms-dev`）
- A 專案 dev 關閉時 B 不會被牽連 502
- `cloudflared tunnel list` 一目了然

Token 是**全 account 共用一張**（SoT 在 Notion；scope 足夠就跨 zone/跨專案 work）。

## Gotchas（精簡版，完整看 cookbook）

- `server.allowedHosts` 漏 hostname → Vite 回 "Blocked request"；第三方路線**必加**
- OAuth callback 仍指 localhost → provider 回 `redirect_uri_mismatch`
- Cookie 沒 `Secure` flag → 跨裝置登入後 session 立刻丟
- Token 缺 `Cloudflare Tunnel:Edit` scope → `route dns` 通但 `tunnel create` 報 403
- Domain 不在 Cloudflare DNS → `route dns` 報 zone not found
- 用 `cloudflared tunnel --url` 拉 quick tunnel → hostname 隨機，OAuth 註冊壞掉；這 skill **MUST** 用 named tunnel
- 對外人分享 dev URL → 用 `vite preview` 而非 `vite dev`（HMR / source files 不對外洩）

完整解法看 `~/offline/clade/vendor/snippets/vite-tunnel/README.md#gotchas`。
