Astroサイトのリンク切れを自動検査する|内部リンク・画像・アンカーの確認方法

Astroのビルド済みHTMLを対象に、内部ページ、画像、ページ内アンカーのリンク切れを検出し、CIで公開を止めるまでの設計と実装手順を解説します。

Astroサイトのリンク切れは、Markdownだけを検索しても十分に見つけられません。共通レイアウトのリンク、変換後の見出しID、srcset内の画像は、最終HTMLを見て初めて確定します。そこでビルド済みファイルを正本にし、内部ページ、画像、アンカーを公開前にまとめて検査します。

リンク切れの種類を分ける

検査対象は次の4種類へ分けます。

  • 内部ページ/kiji/example/: 対応するHTMLがある
  • 静的ファイル/images/guide.webp: 対応するファイルがある
  • ページ内アンカー#setup: 同じHTML内に一致するidがある
  • 別ページのアンカー/guide/#setup: 対象HTMLとidの両方がある

外部URLは一時障害やアクセス制限で結果が揺れるため、内部リンクと同じ基準で毎回のビルドを止めません。まず自分の生成物だけを厳格に検査し、外部URLは警告や別の定期確認へ分けます。mailto:tel:data:などもローカルファイルへ変換せず、種類別に扱います。

対象確認内容初期運用の判定
内部ページ対応するHTMLが出力先にあるかエラー
内部画像・CSS・JavaScript対応するファイルがあるかエラー
ページ内・別ページのアンカー対象HTMLに一致するidがあるかエラー
外部URL応答できるか警告または別の定期確認
href="#"、空のhrefUI上の意図があるか警告から開始

ビルド済みHTMLを対象にする

検査はAstroのビルド後に実行します。Astro公式資料では、astro buildまたはnpm run buildでサイトをビルドし、既定の出力先はdist/です。outDirを変更している場合は、その設定値を検査器にも渡します。

生成後HTMLなら、共通ヘッダーやフッターも含め、実際のidを照合できます。静的生成全体の組み立ては、別の記事で扱います。

HTMLの解析にはHTMLパーサーを使います。正規表現だけでは引用符、属性順、srcsetなどを取りこぼしやすいためです。採用するパーサーと対応Node.js版は※要確認です。少なくともa[href]img[src]source[src]script[src]link[href]を読みます。

内部URLを正規化する

/about/about//about/index.htmlのような表記差を吸収するため、基準URLを与えてURLオブジェクトへ変換し、originpathnamesearchhashへ分けます。Node.js公式資料では、相対URLをnew URL(input, base)で解決できます。

実装ではconst url = new URL(raw, pageUrl)で解決し、url.originがサイトのoriginと同じものだけを内部リンクへ分類します。その後、url.pathnameをファイル確認へ、url.hashをアンカー確認へ渡します。

基準は検査中のHTMLが本番で持つURLです。dist/kiji/sample/index.htmlならhttps://example.com/kiji/sample/のように対応させ、../about/をファイルシステムの現在位置から解決しません。

続いて/dist/index.html/about/dist/about/index.html/feed.xmldist/feed.xmlへ対応させます。ただし、固定変換にせずbuild.formattrailingSlashの設定に合わせます。Astro公式の設定資料では、build.formatの既定値はdirectoryで、/about/index.htmlのように出力されます。fileなら/about.htmlpreserveならソースの構造を保つため、検査器も同じ設定を読む必要があります。

サブディレクトリへ配信するサイトではbaseも考慮します。たとえばbase: '/docs'なら、公開URLの/docs/guide/と出力ファイルの対応を決めてから検査します。クエリはファイル確認から外し、アンカーは後段へ送ります。不正なURLは例外を握りつぶさず、元ページ、属性、値を報告します。

画像とアンカーも確認する

画像はsrcだけでなくsrcsetの候補も確認します。HTML標準では、srcsetは画像候補をカンマで区切り、各候補へ幅記述子(640wなど)または画素密度記述子(2xなど)を付けられます。記述子をパスへ含めず、候補URLだけを一つずつ検査します。ハッシュ付き画像も、最終HTMLとdist/内の実ファイルを照合します。

アンカーは、URLのhashから先頭の#を除き、対象HTML内のid一覧と比較します。WHATWGのHTML標準では、HTML要素のidは同じ要素ツリー内で一意で、1文字以上である必要があり、フラグメントから文書内の特定箇所へリンクする用途にも使われます。したがって検査器では、アンカーの不存在に加えて重複IDも報告対象にできます。

フラグメントはリンク側とid側を同じ規則で比較します。大文字・小文字を勝手にそろえたり、空白をハイフンへ変えたりせず、生成された値の一致を見ます。href=""href="#"はUI仕様によるため、まず警告にし、用途を確認してから失敗へ上げます。

除外ルールを最小限にする

除外は「落ちたURLを消す一覧」ではなく、理由を説明できる規則にします。広い文字列一致で/assets/などを丸ごと除外すると、本物の切れも見えません。認証後だけ開けるURLや配信基盤が生成するURLには、対象、理由、見直し条件を残します。

失敗をすぐ除外せず、元ページ、リンク値、変換後パス、期待した生成物をログへ出します。正規化の不具合なら検査器を直し、本当に検査不能な場合だけ狭く除外します。除外件数の上限は運用実績がないため※要確認です。

CIで失敗させる基準

初回は既存エラーを一覧化して直してから、CIの必須ゲートにします。内部ページ、内部ファイル、明示アンカーの不存在はエラー、外部URLの応答失敗やhref="#"は警告から始めます。同じ原因はまとめつつ、発生元ページはすべて残します。

if (errors.length > 0) {
  for (const error of errors) console.error(error);
  process.exitCode = 1;
}

GitHub公式資料では、終了コード0は成功、0以外は失敗です。検査をビルド直後、デプロイ直前へ置けば、壊れた生成物の公開を止められます。CI全体の組み方は、別の記事で解説します。

合格ログには検査HTML数、抽出URL数、除外数、警告数を残します。失敗時は参照元HTML、属性値、正規化後の対象、不足ファイルまたはIDを出せば再現できます。

ビルド・検査・デプロイを別ジョブにする場合は、デプロイジョブのneedsへ検査ジョブを指定します。GitHub公式のワークフロー構文では、依存先のジョブが失敗またはスキップされると、通常は後続ジョブもスキップされます。ステップの並びだけでなく、ジョブ間の依存関係も明示すると、検査を経由しない公開を防ぎやすくなります。

よくある質問

Astroにリンク切れチェックの標準機能はありますか

この記事で扱う内部ページ、画像、アンカーをビルド済みHTMLから一括照合する検査器は、別途用意する前提です。Astroのビルドが成功しても、リンク先のファイルやidがすべて存在するとは限らないためです。

Markdownファイルを検索するだけでは不十分ですか

不十分な場合があります。共通レイアウトが追加するリンク、Markdown変換後の見出しID、最適化後の画像候補は、生成HTMLで確認する方が公開物に近い結果になります。

外部リンク切れもCIでエラーにするべきですか

最初は警告または別の定期確認へ分けるのが現実的です。相手先の一時障害、アクセス制限、レート制限でも失敗するため、内部リンクと同じ基準にすると公開判断が不安定になります。

href="#"はリンク切れですか

必ずしもリンク切れとは限りません。JavaScriptで開くUIなどに使われることがありますが、意図が不明な場合は警告し、実装を確認してからエラーへ上げます。

アンカーの大文字・小文字や日本語は正規化しますか

検査器が独自に書き換えず、生成されたリンクのフラグメントと対象HTMLのidを同じ復号規則で比較します。空白のハイフン化などを推測すると、本来の不一致を見逃す可能性があります。

関連記事

一次情報

以下は2026-07-24に公式情報を確認しました。

続き(結論と実データ)はnoteに置いています

ここでは手順のところまで書きました。実際に出た数字、うまくいかなかった条件、そのまま使える設定ファイルは、 note の記事にまとめてあります。

noteで続きを読む Xをフォローする