本文へスキップ
Acecore

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

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

はじめに

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

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


View Transitionsのスクリプト問題

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

通常のページ遷移では、ブラウザがHTMLを再パースしてすべてのスクリプトを実行します。しかしView TransitionsはページをDOM差分で更新するため、インラインスクリプトが再実行されません

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

  • ハンバーガーメニューの開閉
  • 検索ボタンのクリックハンドラ
  • ヒーロー画像のスライダー
  • 目次のスクロール追従
  • 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 の構造化データ SearchAction?q= パラメータを定義しておけば、検索結果からサイト内検索に直接遷移できます。URLパラメータを検知して検索モーダルを自動起動する処理を追加しましょう。

キャッシュ設定

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

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

インラインonclickの排除

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

改善パターン

onclickdata-* 属性 + 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?.())
})

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

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

コンポーネント用途
Calloutinfo / warning / tip / note の4種類の注釈
Timelineイベントの時系列表示
FAQ構造化データ対応の質問回答
GalleryLightbox付き画像ギャラリー
CompareTableビフォーアフターの比較表
ProcessFigureステップバイステップのプロセス図
LinkCardOGP風の外部リンクカード
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 7 で削除予定の import { z } from 'astro:content'import { z } from 'astro/zod' に変更しておきます。


定数の一元管理

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

定数集約前の箇所数
AdSense Client ID4ファイル
GA4 Measurement ID2箇所
広告スロットID4ファイル
ソーシャルURL(X・GitHub・Discord・Aceserver)17箇所
電話番号・メール・LINE3ファイル
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 で以下を設定して解消します。

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

まとめ

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

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


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

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

UX改善の進め方

問題の発見

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

パターンの統一

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

検索の実装

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

型安全の確保

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

改善前後の比較

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

改善後
  • astro:after-swapで全スクリプトが正常動作
  • Pagefindで3軸フィルタ付き全文検索
  • TypeScript型安全・定数一元管理
  • addEventListener + data属性でCSP準拠
よくある質問
View Transitionsなしでもこの改善は有効ですか?
スクリプトの初期化パターン以外の改善(Pagefind、TypeScript、定数管理)はView Transitionsの有無に関係なく有効です。
Pagefindはどのくらいのサイト規模まで対応できますか?
Pagefindは静的サイト向けに設計されており、数千ページ規模でも高速に動作します。検索インデックスはビルド時に生成され、ブラウザ側で実行されるためサーバー負荷もありません。
TypeScriptの型エラーは無視しても動きますか?
動作はしますが、型エラーはバグの予兆です。特にAstroのコンテンツスキーマは型安全にすることで、テンプレート内のプロパティアクセスでIDEの補完が効くようになり、開発効率が大きく向上します。
G

Gui

Acecore 代表。システム開発・Web制作・インフラ運用からIT教育まで幅広く手がけるエンジニア。 技術で人と組織の課題を解きほぐすのが好き。

システム開発 Web制作 インフラ運用 IT教育

開発・Web制作のご相談はお気軽に

要件整理からデザイン・実装・運用まで、ワンストップでサポートします。

関連記事

記事を検索