life / docs / design

HTML 文書の静的ホスティング設計

life リポジトリで作った HTML 文書を Cloudflare Pages に置き、URL で読めるようにする。URL の階層はリポジトリの相対パスをそのまま使い、認証は掛けず、検索エンジンには載せない。収集から公開までを bin/pages-deploy.sh が 1 回の実行で行う。

作成
2026-09-09
指示
shika(2026-09-09)
執筆
あおい(秘書)
状態
実装済み・初回公開済み
公開先
shika-notes.pages.dev

指示と、この文書が決めたこと

shika の指示は次の 5 点だった。

  1. life で作った HTML を静的ホスティングし、Cloudflare Pages に上げていく
  2. 認証は不要。URL を知っている人は誰でも読めてよい
  3. 認証情報は life ルートの .env にある CF_TOKEN_PERSONAL と CF_ACCOUNT_PERSONAL を参照する
  4. 実装計画や調査レポートのような「自分が読むもの」は Markdown ではなく HTML で出力し、Pages に上げる
  5. 階層は「各プロジェクト」と「それ以外」に分けて提案する

この文書は、階層の提案(採用案と採らなかった案)、公開の仕組み、静かに壊れる箇所、受け入れ基準、残課題を記録する。規約の正本は life ルートの CLAUDE.md の節「人が読むレポートは Markdown ではなく HTML で書き、Cloudflare Pages に公開して URL を報告する」で、この文書はその根拠側である。

公開先と現在の状態

項目値確認方法・出典
Cloudflare Pages プロジェクトshika-notes(shika が 2026-09-08 14:33 UTC に作成済み。Git 連携なしの直接アップロード型)Pages API のプロジェクト一覧を 2026-09-09 00:50 JST 頃に取得。当時デプロイは 0 件だった
本番 URLhttps://shika-notes.pages.dev/プロジェクトの domains フィールド
初回デプロイ2026-09-09 00:55 JST 頃、10 ファイル。デプロイ固有 URL は https://67811c63.shika-notes.pages.devbin/pages-deploy.sh の出力
認証情報life ルートの .env(*.env で git 無視。git check-ignore -v .env が .gitignore:9 を返す)スクリプトは CF_TOKEN_PERSONAL → CLOUDFLARE_API_TOKEN、CF_ACCOUNT_PERSONAL → CLOUDFLARE_ACCOUNT_ID に載せ替える。環境変数名は wrangler の公式ページによる
アップロード手段npx --yes wrangler@4.86.0 pages deploy <ステージング> --project-name shika-notes --branch main --commit-dirty=truewrangler pages deploy の公式リファレンス。バージョンは 2026-09-09 時点で npx が解決した最新を固定した
このトークンはアカウント所有のトークンである。ユーザー所有トークン用の検証エンドポイント /user/tokens/verify は「Invalid API Token」を返すが、アカウント配下の /accounts/<ID>/tokens/verify は status: active を返した(2026-09-09 01:00 JST 実測)。権限を狭めるときは Cloudflare ダッシュボードのアカウント側の API トークン画面で編集する。

URL の階層(提案と採用)

採用: リポジトリの相対パスをそのまま URL にする

「各プロジェクト」と「それ以外」の分け方は、既にリポジトリのディレクトリ構成がそうなっている。projects/<プロジェクト名>/ がプロジェクトごとの置き場で、それ以外の HTML は research/(単発調査)・docs/(横断の設計・規約)・sessions/(セッションの副産物)・pokemon/(対戦ログ)にある。URL 用の階層を別に発明すると、ファイルと URL の対応表が要るようになり、その表が古くなる。だから発明しない。

https://shika-notes.pages.dev/
├── /                                   トップ索引(pages-deploy.sh が生成。プロジェクト別・調査・設計・セッション・ポケモン)
├── /projects/<プロジェクト名>/…          各プロジェクトの文書(要件定義・実装計画・手順書・設計図)
│     例 /projects/mycocktails/requirements
│        /projects/PersonalOS/docs/overview
├── /research/                          調査レポートの索引(research/index.html。bin/research-index.sh が生成)
│   └── /research/<カテゴリ>/YYYY-MM-DD_<slug>
├── /docs/design/YYYY-MM-DD_<slug>         横断の設計・規約(この文書もここ)
│        /projects/battle-feedback/battles/<試合>/feedback   対戦フィードバック(別リポジトリ。docs/** と feedback.html だけ。frames/ と JSON は上げない)
├── /sessions/<セッション>/<文書>          セッションの副産物(.snapshots/ は除く)
└── /pokemon/battle-logs/YYYY-MM-DD_<slug>  対戦ログ

Pages は /x.html への要求を /x へ、/dir/index.html を /dir/ へ転送する(公式の配信規則。実測ではステータス 308)。報告に書く URL は転送後の .html を省いた形に統一する。HTML 同士の相対リンク(../projects/…/x.html のような)はパス構造が同じなのでそのまま動く。

採らなかった案

プロジェクトごとに Pages プロジェクトを分ける案(mycocktails.pages.dev のように)は採らない。プロジェクトを跨ぐ相対リンク(調査索引からプロジェクト文書へのリンクなど)が切れる。トークンで作れるプロジェクト数の上限は 100(公式 limits)で数の問題ではなく、リンクの問題である。
URL 用の平らな階層を作る案(/reports/2026-09-08_xxx のように日付だけで並べる)は採らない。「調査レポートは索引がリンクで拾い、ファイルは動かさない」という 2026-09-07 の決定(bin/research-index.sh 冒頭)と同じ理由で、対応表を持つと壊れる。
Cloudflare Access で認証を掛ける案は、shika の指示(URL を知っている人は誰でも読めてよい)により採らない。代わりに検索エンジンに載らないよう _headers で全パスに X-Robots-Tag: noindex を付ける(公式の _headers 仕様)。後で認証が要るなら、Pages プロジェクトに Access ポリシーを付けるだけでスクリプトは変えなくてよい。
リポジトリ内の *.html を全部上げる案は採らない。docs/profile/my-data.md のように外へ出してはいけない平文が life にはある。今は Markdown なので対象外だが、いつか誰かが HTML 化したときに黙って公開される経路を作らない。公開対象は列挙で固定する。
ファイル保存のたびに自動でデプロイするフックは採らない。エージェントは 1 本の HTML を書き上げるまでに何度も保存するので、書きかけが何度も公開される。「書き終えたら 1 回叩く」を規約にし、その規約は CLAUDE.md に置いた。

公開の仕組み(bin/pages-deploy.sh)

  1. 収集: 列挙されたパターン(projects/*/*.html・projects/*/docs/*.html・research/**/*.html・sessions/*/*.html・pokemon/**/*.html・docs/**/*.html、および別リポジトリ projects/battle-feedback/ の docs/**/*.html と battles/*/feedback.html。shika の指示 2026-09-09 で追加)から HTML を集める。submodule と外部リポジトリの clone、.snapshots/ は除く。<head> に <meta name="pages-publish" content="no"> がある HTML は除外し、除外した件数と名前を毎回表示する(0 件でも「0 件」と出す)
  2. 索引: 先に bin/research-index.sh を走らせて research/index.html と reports.json を最新にしてから集める。トップの index.html(プロジェクト別・調査・設計・セッション・ポケモンの 5 区分)と 404.html、_headers をステージングに生成する
  3. 検査: 25 MiB を超えるファイル(Pages の 1 ファイル上限。公式 limits)があれば上げる前に終了コード 1 で止まる。<title> の無い HTML は警告し、公開後に終了コード 2 を返す
  4. 認証: .env から 2 つのキーだけを読む(set -a でファイル全体を export しない)。無ければ終了コード 1 で止まり、理由にキー名を出す
  5. 公開: 固定バージョンの wrangler で pages deploy。成功したら本番 URL と、引数で渡されたファイルの公開 URL を並べる
bin/pages-deploy.sh research/technology/2026-09-08_xxx.html   # 収集→索引→公開→この文書の URL を表示
bin/pages-deploy.sh --dry-run                                  # 公開せず対象一覧と件数だけ
bin/pages-deploy.sh --url projects/mycocktails/requirements.html   # URL だけ計算
bin/pages-deploy.test.sh                                       # 契約の検査(ネットワークに出ない)

静かに壊れる箇所(気づけないこと)

何が起きるか気づけない理由手当て
Pages は 404.html が無いと単一ページアプリと見なし、未知のパスにトップページを返す(公式)リンク切れが「索引が表示される」という形になり、ステータスも 200 で、エラーが出ないスクリプトが必ず 404.html を同梱する。実測で /no/such/page と /docs/profile/my-data.md が 404 を返すことを確認した(2026-09-09 00:57 JST)
battle-feedback の仕様書は同じリポジトリの README.md・pipeline/README.md・sets/*.json へ相対リンクしているMarkdown と JSON は公開対象外なので、そのリンクは Pages 上で 404 になる。HTML 同士のリンクは生きているので、壊れているのが一部だけで気づきにくい404 ページに「公開対象に入っていないかパスが変わっている」と書いてある。Markdown を HTML 化するかは残課題の検討事項
列挙に無い場所に置いた HTML は公開されない「書いた」「上げた」と報告しても、その URL は 404 になる。スクリプトはその場所を見ていないので警告しない引数で渡したファイルが公開対象に無いとき、スクリプトは「公開対象に入っていない」と URL の代わりに表示する。置き場所の規約は CLAUDE.md
ステージングの中にマニフェストを書くと、それもアップロードされる1 ファイル増えるだけで、デプロイは成功するマニフェストはステージングの外(<ステージング>.manifest.json)に書く
wrangler のメジャーバージョンが上がると、オプション名や出力形式が変わりうるnpx wrangler は黙って最新を取る。URL の抽出(出力から *.pages.dev を拾う)だけが空になるバージョンを wrangler@4.86.0 に固定。上げるときはこの文書を更新する
個人の機微情報を含む HTML を公開対象の場所に置く認証が無いので、URL が漏れれば誰でも読める。公開は成功し、エラーは出ない規約(CLAUDE.md)で「置かないか、除外メタを付ける」と定める。機械的な検知は未実装(残課題)

受け入れ基準(落ちるべきときに落ちることを確かめたもの)

いずれも 2026-09-09 に実行し、スクリプトをわざと壊して落ちることも見た(除外判定を無効化すると 4 本、404.html の生成を止めると 1 本が落ちる)。判別力の預け先は、データに依存しない最小入力の一時ディレクトリ。

叩くこと期待する出力落ちたら
bin/pages-deploy.test.sh; echo "exit=$?"最後の 2 行が ALL OK と exit=0❌ の行の検査名に対応する説明が同スクリプト冒頭のコメントにある。bin/pages-deploy.sh --dry-run で除外・件数の実値を見る
bin/pages-deploy.sh --dry-run >/dev/null 2>&1; echo "exit=$?"exit=0(<title> 無しがあれば 2)bin/pages-deploy.sh --dry-run を再実行し、⚠️ の行のファイルに <title> を付ける
curl -s -o /dev/null -w '%{http_code} %header{x-robots-tag}\n' https://shika-notes.pages.dev/no/such/page404 noindex200 なら 404.html が同梱されていない。bin/pages-deploy.sh --dry-run のあと PAGES_STAGE_DIR=/tmp/s bin/pages-deploy.sh --dry-run; ls /tmp/s/404.html
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' https://shika-notes.pages.dev/research/index.html308 https://shika-notes.pages.dev/research/Pages の転送規則が変わった可能性。--url の変換規則(スクリプトの to_url)を見直す
git check-ignore -v .env; echo "exit=$?".gitignore:9:*.env .env と exit=0🔴 コミットする前に止まる。.gitignore の *.env の行を戻す

ロールバック: デプロイは Cloudflare のダッシュボード(Pages → shika-notes → Deployments)から過去のデプロイに戻せる。スクリプトと規約の変更は git で戻せる。.env はこの作業で触っていない。

残課題

実測タスク(叩けば答えが出る)

検討事項(判断が要る)