Site search / サイト内検索

必要な情報を探す

ページ内の言葉を探す検索と、意味が近い内容を探す検索を使い分けられます。

「検索する」を実行すると、入力した検索語をCloudflare Workers AIで数値表現に変換し、その数値表現をCloudflare Vectorizeで当サイトの公開情報と照合します。あわせて、Acecore共通検索API(acecore.net)へ送信し、 Acecore関連サイトの公開情報を下部に表示することがあります。個人情報や機密情報は入力しないでください。個人情報の取り扱い

Insights / 技術解説

Astro View Transitionsの落とし穴と解決策 ― UX・コード品質改善ガイド

AstroのView Transitionsでスクリプトが動かなくなる問題の解決策、Pagefind全文検索の導入、TypeScript型安全性の向上、定数の一元管理など、UXとコード品質を改善した実践ガイドです。

  • 技術
  • Astro
  • Webサイト
Astro View Transitionsの落とし穴と解決策 ― UX・コード品質改善ガイド
この記事の目次
  1. はじめに
  2. View Transitionsのスクリプト問題
  3. なぜスクリプトが動かなくなるのか
  4. 解決パターン
  5. astro:after-swap と astro:page-load の使い分け
  6. Pagefind 全文検索の導入
  7. 基本構成
  8. ファセット検索
  9. 検索モーダル
  10. SearchAction連携
  11. キャッシュ設定
  12. インラインonclickの排除
  13. 改善パターン
  14. コンポーネントライブラリの整備
  15. TypeScript 型安全性の向上
  16. any型の排除
  17. コンテンツスキーマのリテラル型
  18. as const アサーション
  19. 非推奨インポートの移行
  20. 定数の一元管理
  21. その他のUX改善
  22. 目次のスクロール追従
  23. スクロールスパイ
  24. ページネーション
  25. スティッキーヘッダーのアンカーリンク
  26. まとめ
  27. この記事が含まれるシリーズ
  28. 2026年10月6日追記:Editorの表示と操作状態

ページ遷移後だけメニューや検索が動かない場合は、直接表示とClientRouter経由の表示を比べ、初期化するタイミングを調べます。Astro: View Transitionsのライフサイクルに沿ってイベント登録を整理し、戻る・進むを含めて重複実行も確認してください。

はじめに

AstroのView Transitions(ClientRouter)は、ページ遷移をSPAのようにスムーズにする強力な機能です。しかし導入した途端、ハンバーガーメニューが開かない、検索ボタンが反応しない、スライダーが止まる……という問題に直面します。

この記事では、View Transitionsの落とし穴とその解決策、そしてUXとコード品質を改善するための実践的な手法を紹介します。


View Transitionsのスクリプト問題

なぜスクリプトが動かなくなるのか

通常のページ遷移ではブラウザがHTMLを読み直します。ClientRouterではスクリプトの再実行条件が異なり、バンドル済みのモジュールスクリプトは初回だけ実行されます。インラインスクリプトは遷移によって再実行される場合があります。

影響を受けるのは以下のような処理です。

  • ハンバーガーメニューの開閉
  • 検索ボタンのクリックハンドラ
  • ヒーロー画像のスライダー
  • 目次のスクロール追従
  • YouTube埋め込みのファサードパターン

解決パターン

すべてのスクリプトを 名前付き関数に包み、astro:after-swap で再登録 するパターンに統一します。

<script>
  function initHeader() {
    const menuBtn = document.querySelector("[data-menu-toggle]");
    menuBtn?.addEventListener("click", () => {
      /* ... */
    });
  }

  // 初回実行
  initHeader();

  // View Transitions後に再実行
  document.addEventListener("astro:after-swap", initHeader);
</script>

astro:after-swap と astro:page-load の使い分け

  • astro:after-swap:DOMが差し替えられた直後に発火。初回読み込みでは発火しないため、関数を直接呼ぶ必要がある
  • astro:page-load:初回読み込み と View Transitions後の両方で発火する。初回呼び出しの記述を省略できる

YouTube埋め込みのように初回読み込みでも確実に動作させたい場合は astro:page-load が便利です。


Pagefind 全文検索の導入

静的サイトに全文検索を実装するなら Pagefind がおすすめです。ビルド時にインデックスを生成し、ブラウザ側で検索を実行するため、サーバー不要で高速です。

基本構成

{
  "scripts": {
    "build": "astro build && pagefind --site dist"
  }
}

Astroのビルド後に Pagefind を実行し、dist/pagefind/ にインデックスを出力します。

ファセット検索

data-pagefind-filter 属性を使えば、著者・年・タグの3軸でフィルタリングできます。

<span data-pagefind-filter="author">gui</span>
<span data-pagefind-filter="year">2026</span>
<span data-pagefind-filter="tag">Astro</span>

検索モーダル

Ctrl+K ショートカットで開く検索モーダルを実装します。ゼロ結果時には記事一覧・サービスページ・お問い合わせへのリンクを表示し、ユーザーの離脱を防ぎます。

SearchAction連携

Googleのサイト内検索ボックスは2024-11に終了しました(公式の終了案内)。既存の SearchAction は当時の実装として扱い、検索結果に検索枠が出るとは期待しません。?q= を受けて検索モーダルを開く処理は、共有リンクなどサイト内の検索導線として使えます。

キャッシュ設定

Pagefindのインデックスファイルは変更頻度が低いため、Cloudflare Pages のヘッダー設定でキャッシュを有効にします。

/pagefind/*
  Cache-Control: public, max-age=604800, stale-while-revalidate=86400

インラインonclickの排除

HTMLに直接書く onclick="..." は手軽ですが、CSP(Content Security Policy)で unsafe-inline を要求する原因になります。

改善パターン

onclick を data-* 属性 + addEventListener に置き換えます。

<!-- Before -->
<button onclick="window.openSearch?.()">検索</button>

<!-- After -->
<button data-search-trigger>検索</button>
document.querySelectorAll("[data-search-trigger]").forEach((btn) => {
  btn.addEventListener("click", () => window.openSearch?.());
});

コンポーネントライブラリの整備

ブログ記事を書くときに使えるコンポーネントを揃えておくと、記事の表現力が上がります。

コンポーネント 用途
Callout info / warning / tip / note の4種類の注釈
Timeline イベントの時系列表示
FAQ 構造化データ対応の質問回答
Gallery Lightbox付き画像ギャラリー
CompareTable ビフォーアフターの比較表
ProcessFigure ステップバイステップのプロセス図
LinkCard OGP風の外部リンクカード
YouTubeEmbed ファサードパターンで遅延読み込み

これらはすべてMarkdownのフロントマターから呼び出せるように設計しています。記事のテンプレートで data.callout があれば <Callout> を描画する、という仕組みです。


TypeScript 型安全性の向上

any型の排除

any[] → CollectionEntry<'blog'>[] のように具体的な型を指定します。IDEの補完とコンパイル時のエラー検出が効くようになり、テンプレート内のプロパティアクセスが安全になります。

コンテンツスキーマのリテラル型

type: z.enum(["info", "warning", "tip", "note"]).default("info");

フロントマターの値をリテラル型ユニオンで定義すると、テンプレート側で if (callout.type === 'info') のような分岐が型安全になります。

as const アサーション

定数オブジェクトに as const を付けると、プロパティが readonly になり、型推論がリテラル型になります。SITE 定数には必ず付けましょう。

非推奨インポートの移行

現在のAstro公式ドキュメントでは、コンテンツスキーマの z は astro/zod から読み込みます。公式のコンテンツコレクションガイドも参照してください。


定数の一元管理

ハードコードされた値は変更時に見落としの原因になります。以下の値を src/data/site.ts に集約しました。

定数 集約前の箇所数
AdSense Client ID 4ファイル
GA4 Measurement ID 2箇所
広告スロットID 4ファイル
ソーシャルURL(X・GitHub・Discord・Aceserver) 17箇所
電話番号・メール・LINE 3ファイル
export const SITE = {
  name: "Acecore",
  url: "https://acecore.net",
  ga4Id: "G-XXXXXXXXXX",
  adsenseClientId: "ca-pub-XXXXXXXXXXXXXXXX",
  social: {
    x: "https://x.com/acecore",
    github: "https://github.com/acecore-systems",
    discord: "https://discord.gg/...",
  },
} as const;

その他のUX改善

目次のスクロール追従

IntersectionObserver でコンテンツの見出しを監視し、アクティブな見出しをサイドバーの目次にハイライトします。scrollIntoView({ block: 'nearest', behavior: 'smooth' }) で目次自体もスクロールさせるのがポイントです。

スクロールスパイ

サービスページのようなシングルページ構成では、IntersectionObserver でナビゲーションのアクティブ項目を自動追従させます。

ページネーション

6記事ごとの自動ページ分割、省略記号付きナビゲーション(1 2 ... 9 10)、「← 前へ」「次へ →」テキストリンクを実装します。ページネーションのロジックは src/utils/pagination.ts に共通化しましょう。

スティッキーヘッダーのアンカーリンク

スティッキーヘッダーがあると、アンカーリンク先がヘッダーに隠れることがあります。次は当時使用していたUnoCSSのpreflight設定例です。現在のTailwind環境では同じ目的のCSSを適用します。

[id] {
  scroll-margin-top: 5rem;
}
html {
  scroll-behavior: smooth;
}

まとめ

View Transitionsを使うなら、スクリプトの初期化パターンを統一する ことが最重要です。astro:after-swap / astro:page-load の使い分けを理解し、すべてのインタラクションをテストしましょう。

コード品質の面では、TypeScriptの型安全性と定数の一元管理が長期的な保守性に大きく貢献します。最初は面倒に感じますが、IDEの補完が効くようになる恩恵は日々の開発で実感できます。


この記事が含まれるシリーズ

この記事は「Astroサイトの品質改善ガイド」シリーズの一部です。パフォーマンス・SEO・アクセシビリティの改善についても個別の記事で紹介しています。

2026年10月6日追記:Editorの表示と操作状態

匿名化したEditor改修では、狭い画面のカテゴリ移動、固定ナビゲーション、キーボード用の案内、項目ラベルとfocusを調整しました。公開・非公開の表示も、ボタンの見た目から推測するのではなく、APIの状態と公開snapshotへ合わせます。

認証の継続情報やfrontend/backendの契約が欠けた場合は、保存成功として表示せず、再読込・再認証の必要性を伝えます。空の取込画面が整ったことは、実データの取込から保存・公開までの受入の証拠ではありません。入力側の実装範囲はプロフィール取込、公開CSSはテーマの安全設計に分けて記載しました。

名称や分類を再編する場合は、既存の値、API、filter、DB変更を合わせます。旧データが0件なら、実コンテンツを移行した実績にはしません。状態バッジが詳細へのリンクならクリックとEnterの両方で到達できるようにします。公開URLの識別子をインライン編集する場合も、形式・重複・URL反映の検証を残します。公開表示・メディア再生・匿名時のログイン誘導の確認と、ログイン済みEditorでの識別子保存や公開状態変更の受入は別です。

従来の編集領域を、利用者の新しい作業単位へ組み替えるときは、永続ナビと作業領域を分け、名称だけを変えて旧操作を残さないようにします。残高などの補助数値は主要操作より視覚的に弱くしても、文字サイズとcontrastを保ち、読めなくしない階層にします。

操作、APIの結果、表示する状態を分ける 画面上の操作だけでは、保存・公開済みとは判定しません。
  1. Editorでの操作 カテゴリ移動や作業単位の選択は画面上の操作で、保存・公開が済んだことを示すものではありません。
  2. APIと公開snapshot APIの結果と公開snapshotを確認します。通信失敗や認証情報の欠落を成功扱いしません。
  3. 再表示後の状態 確認できた状態だけ反映し、必要なら再読込・再認証を案内します。ログイン利用者の保存・公開受入は未確認です。

UX改善の進め方

  1. 問題の発見

    View Transitions導入後の動作不良をリストアップ。

  2. パターンの統一

    すべてのスクリプトを統一された初期化パターンに変換。

  3. 検索の実装

    Pagefindで全文検索を導入し、導線を整備。

  4. 型安全の確保

    any型の排除・定数一元管理で保守性を向上。

改善前後の比較

改善前

  • ページ遷移後にハンバーガーメニューが動かない
  • サイト内検索なし
  • any型やハードコード定数が散在
  • インラインonclickでCSP違反リスク

改善後

  • astro:after-swapで全スクリプトが正常動作
  • Pagefindで3軸フィルタ付き全文検索
  • TypeScript型安全・定数一元管理
  • addEventListener + data属性でCSP準拠

よくある質問

View Transitionsなしでもこの改善は有効ですか?

スクリプトの初期化パターン以外の改善(Pagefind、TypeScript、定数管理)はView Transitionsの有無に関係なく有効です。

Pagefindはどのくらいのサイト規模まで対応できますか?

Pagefindは静的サイト向けに設計されており、数千ページ規模でも高速に動作します。検索インデックスはビルド時に生成され、ブラウザ側で実行されるためサーバー負荷もありません。

TypeScriptの型エラーは無視しても動きますか?

動作はしますが、型エラーはバグの予兆です。特にAstroのコンテンツスキーマは型安全にすることで、テンプレート内のプロパティアクセスでIDEの補完が効くようになり、開発効率が大きく向上します。