開発family-trips

きっかけ

「家族旅行の計画を Cloudflare にデプロイしたい」と思い立った。8月の大分帰省を皮切りに、行き先ごとに地図とガントチャートを置いて、何泊どこに泊まって何をしたかを後から見返せるアーカイブにしたい。

ホテルブッキングや航空券の予約画面はすぐ消えるし、メモアプリだと地図が貼れない。自分のサイトとして残せば、子供が大きくなったときに親が辿った旅程をそのまま見せられる。

新規プロジェクト family-trips をローカルに切って、Claude Code に「ゼロから組んで」と頼んだ。手は動かさず、フレームワーク選定とつまずきポイントの判断だけする立場で進めた。

なぜ Nuxt ではなく Astro にしたか

普段は Nuxt 4 でブログを動かしているので、横展開で Nuxt を使う手もあった。Claude Code に選ばせたら Astro が出てきた。理由を読んで、これは Astro でいいと納得した。

  • コンテンツコレクションがアーカイブ構造と噛み合う: 「旅行ごとに1ページ」のサイトは、src/content/trips/2026-08-oita.md を置くだけで型付きルートが生える Astro の構造と相性が良い
  • SSG に振り切れる: 旅行記は基本的に静的で、サーバー側で動的処理を回す要件がない。Astro はビルド後の dist/ をそのまま配ればよく、Nuxt の SSG 設定で過去にメモリ問題を踏んだ経験から、構造がシンプルなほうが安心できた
  • JS が軽い: 地図表示(Leaflet)と多少のドロワー開閉だけ動けばよく、フレームワーク全体を JS で動かす必要がない

Vue インテグレーションを足せば、必要なところだけ Vue コンポーネントを置ける。実際に地図コンポーネントは Vue で書いた。

pnpm の minimumReleaseAge で Astro が弾かれた

pnpm create astro@latest を回したら、Astro 6.3.3 のインストールでエラーが出て止まった。Claude Code に原因を追わせたら、pnpm の minimumReleaseAge 設定が引っかかっていた。

これはサプライチェーン攻撃の対策で、リリースから一定期間(自分は3日に設定)経っていないパッケージを自動で弾く仕組み。Astro 6.3.3 は2日前リリースで条件を満たさず、インストールが拒否されていた。

設定を緩める選択肢もあったが、緩めずに Astro 側のバージョンを下げて回避するほうが筋がいい。10日前リリースの 6.3.1 を package.json で明示的に指定して、再インストールでパスした。

学び: pnpm の minimumReleaseAge は、悪意あるパッケージが公開直後に拡散する前にブロックする防波堤として効く。発生から24時間以内に検知・取り下げされるケースが多いので、3日待つだけで攻撃成功率がかなり下がる。ハマったときに「設定を消す」ではなく「バージョンを下げる」で逃げるのが正しい振る舞い。

Cloudflare Pages は Astro アダプター不要

Astro には Cloudflare アダプターがあるが、今回は使わなかった。Cloudflare Pages は静的ファイルを配るだけのモードで動かせるので、pnpm builddist/ をそのままアップロードすれば動く。SSR を後から欲しくなったらアダプターを入れる、で十分だと判断した。

Vue インテグレーションと Leaflet を足して、

  • BaseLayout.astro(共通レイアウト + サイトナビ)
  • index.astro(トップページ。旅行一覧のテーブル)
  • [...slug].astro(動的に旅行詳細ページ)
  • TripMap.vue(Leaflet で地図表示。OpenStreetMap タイル使用で API キー不要)

をひと通り組み上げてもらった。地図タイルが API キー不要で動くので、設定の初期コストがゼロで済むのが嬉しい。

GitHub にプッシュして Cloudflare Pages で配信

ローカルでビルドが通ったところで、GitHub の private リポジトリ(keikomatsu/family-trips)を作って main ブランチに push してもらった。gh CLI で一気に流れた。

最初は Wrangler から Direct Upload モードでデプロイした。CLI から dist/ をそのままアップロードする方式で、すぐ動いた。

ところがデプロイ後に「GitHub と連携してプッシュごとに自動ビルドさせたい」と気が変わった。ここで罠を踏んだ。

学び: Cloudflare Pages の Direct Upload モードGitHub 連携モードは、後から相互変換できない。GitHub 連携にしたければ、Direct Upload で作ったプロジェクトを削除して、ダッシュボードから OAuth フローで作り直すしかない。

Direct Upload で作っていたプロジェクトを Wrangler で削除して、ダッシュボードで GitHub 連携モードのプロジェクトを新規作成し直した。OAuth はブラウザでクリックする工程が残るので、そこだけ自分で踏んだ。

Cloudflare Pages の Node.js バージョン指定は NODE_VERSION=22 を環境変数に入れておけば、最新の 22.x として解決される。Astro 6 の要件(>=22.12.0)を満たすので、これで放置できる。

家族メンバーだけが見られるようにする方針

サイト全体を世界に公開する気はない。家族の旅程・実家の位置・宿泊先がそのまま地図に出ているので、外に出るとプライバシー的にまずい。

ロックは Cloudflare Access(Cloudflare Zero Trust の一部)で掛ける方針にした。家族のメールアドレスをホワイトリスト登録すると、サイトを開いたとき One-time PIN がメールで届き、PIN を入力した人だけが中身を見られる。

  • パスワード設定もアプリ側の認証実装も不要
  • メールアドレス単位で許可を出すので、家族数名分の登録で済む
  • Cloudflare 側で完結するので、Astro 側のコードに認証ロジックを書かなくていい

これは今日は方針を決めただけで、設定は次のセッションに回した。デプロイされた *.pages.dev がまだ公開状態なので、早めに閉じる必要がある。

Wikimedia 画像のホットリンクで ORB に弾かれた

トップページに「滞在ガイド」セクションを足して、福岡・札幌・沖縄・台北の代表写真を Wikimedia から拾って表示しようとしたら、画像が出ない。Chrome の Network パネルを見たら ORB(Cross-Origin Read Blocking)でブロックされていた。

試したこと結果
Wikimedia の画像 URL を <img src> で直接参照ORB でブロック。localhost からのホットリンクを Wikimedia が拒否
wget で User-Agent 指定なしで取得Wikimedia のホットリンク規約で 403 拒否
具体的な User-Agent を付けて再取得サイズが大きすぎて「Use thumbnail size」エラー
800px 指定で再取得サイズ違反で再度エラー
500px 指定で再取得成功。画像が落ちてきた

最終的に public/images/guides/ にダウンロードして、同一オリジンから配信する形に切り替えた。ホットリンクで楽しようとすると Wikimedia のポリシーに弾かれるので、最初から落として持つのが筋。

学び: Wikimedia の画像をサイトに使うなら、ホットリンクではなくダウンロードしてセルフホストするのが前提。User-Agent と画像サイズ(500px 推奨)の2点を押さえれば API キーなしで取得できる。

出来上がったもの

ローカルで pnpm dev を立ち上げて、Chrome DevTools で確認したら、トップページのテーブル一覧と大分旅行ページの地図ピンが両方表示された。HTTP 200 OK、コンソールエラーなし。リポジトリは GitHub に上がっていて、Cloudflare Pages 連携で自動ビルドが回っている。

旅行ページ側はその後さらに発展して、ガントチャートで「飛行機 / レンタカー / 実家泊 / 観光 / 食事 / 外泊 / 仕事」のレーンを並べる縦長レイアウトに作り直した。ここは別の日のログに残す。

振り返り

  • フレームワーク選定で「Nuxt の横展開」ではなく「目的に合うもの」を選び直せたのは Claude Code に提案させた効果が大きかった。自分一人で決めると慣れた選択肢に寄りがち
  • pnpm の minimumReleaseAge で躓いたのは、最初は「インストールエラー」としか出ないので原因に辿り着くまで時間がかかる。今日メモを残しておけば、次に新規プロジェクトで同じ警告を見たときに10秒で原因を特定できる
  • Cloudflare Pages の Direct Upload と GitHub 連携の相互変換不可は知らないと事故る。Direct Upload で動かしてみてから本番運用を GitHub 連携にする、というよくありそうな手順が踏めない
  • Wikimedia の画像は最初からダウンロードしてセルフホストする前提で組むのが正解。試行錯誤の途中で「とりあえずホットリンクで動かす」をやってもどうせ後で全部置き換えになる

明日以降は Cloudflare Access の設定と、福岡経由ルートの比較ページを進める。