---
title: a11y/duplicate-landmark · ランドマークの重複
description: 1つのルートに main・banner・contentinfo ランドマークはそれぞれ1つまでにしましょう。
---

**重大度:** warning · **カテゴリ:** a11y

## チェック内容

ルートを構成するレイアウトチェーン（`+layout.svelte` から `+page.svelte` まで）と、そこから解決したローカルコンポーネントを合わせて、`main`・`banner`・`contentinfo` のいずれかのランドマークが2つ以上現れるルートを検出します。分岐を考慮して判定し、`{#if}`/`{#await}` ブロックでは出現数が最も多いアーム（同数ならドキュメント順で先のアーム）だけを数えるため、1 つの `{#if}` のアーム同士が合算されることはありません。ただし**別々の** `{#if}` ブロック同士は独立です（条件式を評価するわけではないため）。また `{#each}`/`{#snippet}` の中身は0〜N回描画されるため対象外です。`navigation`（`<nav>`）と `complementary` は意図的にこの集合の外です: APG はページ内にそれぞれ複数あることを認めています。

ランドマークはファイルをまたいで検出します。`+layout.svelte` の `<main>` と `+page.svelte` の別の `<main>` は、1つのルートに `main` ランドマークが2つある状態です。レイアウトの `<main>` と、インポートした `$lib` コンポーネントが描画する `<main>` の組み合わせも同様です。`<main>` および明示的な `role="banner"`/`role="contentinfo"` は、ルートを構成するどこにあっても数えます。一方 `<header>`/`<footer>`（暗黙の `banner`/`contentinfo` ランドマーク）は、チェーン内のファイルのテンプレート最上位にある場合のみ数えます。ネストしたコンポーネントの中にある場合、それを使う側のコンポーネント次第でセクショニングコンテンツの中に置かれる可能性があり、そこまで数えると存在しない重複を作り出しかねないためです。

3種類のランドマークがいずれも存在しないルートは、何も出力しません。

## なぜ重要か

支援技術のユーザーは、main・banner・contentinfo といったランドマーク間をキー操作でジャンプし、繰り返しの定型部分を飛ばしてページの本文にたどり着きます。同じ種類のランドマークが複数あると、このジャンプ先が曖昧になります。どちらが本当のページ本文で、どちらが本当のサイトヘッダーなのか分からなくなるのです。

## 修正方法

1つのルートにつき `<main>`、`<header>`/`role="banner"`、`<footer>`/`role="contentinfo"` をそれぞれ1つに保ちます。レイアウトが既にこれらのどれかを描画しているなら、ページ側（やインポートしたコンポーネント）で重ねて描画しないようにします。

```svelte +layout.svelte
<header>すべてのルートに表示するナビゲーション</header>
<main>{@render children()}</main>
<footer>すべてのルートに表示するフッター</footer>
```

```svelte +page.svelte
<h1>ページの本文</h1>
<!-- ここに2つ目の <main> を置かない -->
```

## モードによる違い

両モードともランドマークを収集しますが、収集元が異なるため結果が食い違うことがあります。

- **ソース解析**（CLI、ダッシュボードの静的ベースライン）は、ルートのレイアウトチェーンと、そこから解決したローカルコンポーネントを、分岐を考慮した判定で合成します。`{#if}`/`{#await}` ブロックでは出現数が最も多いアームだけを数えるため、実際にはレンダリングされないアームを選んでしまうことがあります。解決できないコンポーネント（`node_modules` や動的に選ばれるコンポーネント）が持つランドマークは見えず、`{#each}`/`{#snippet}` の中身は0〜N回描画されるため対象外です。
- **ソース解析**（CLI、ダッシュボードの静的ベースライン）はまた、`<header>`/`<footer>` がランドマークかどうかを、自ファイル内の位置で決めます。**チェーンファイル**（レイアウトまたはページ）のテンプレートのトップレベルにあるときだけ `banner`/`contentinfo` と数え、コンポーネント内では数えません。コンポーネントの header は、そのコンポーネントから見えないセクショニング内容の下にあるかもしれないためです。これは両方向の近似です: コンポーネント自身のトップレベルの `<header>` は数えないので、同じコンポーネントが 1 ルートに 2 つあっても描画後の DOM のようには `banner` の重複になりません。一方、ページのトップレベルの `<header>` は、レイアウトがページを `<main>` の中に描画していても数えます。描画後の DOM ならランドマークとして扱われない場所です。
- **レンダリング解析**（Vite プラグインのビルド、ダッシュボードで訪問したルート）はレンダリング済みの HTML を読みます。そのため、実際にレンダリングされた分岐だけを認識し、`{#each}` が生成したランドマークもすべて数えます。ソースファイルを持たないため、検出結果の位置は特定のファイル・行ではなくルート自体に紐づきます。同じ不具合でも、永続化される検出キーはモードによって異なります。

両者が食い違う場合は、レンダリング結果を信頼してください。ブラウザに配信される内容を反映しているのはそちらです。

## 無効化

検出が指す行の直上にインラインの `svelte-vitals-disable-next-line` コメントを置けば抑制できます（ソース解析のみ。ビルドの検出はプリレンダリングされた HTML を指すため、直上に置くべきソース行がありません）。その行が合成されたコンポーネント内にある場合、1 つのディレクティブがそれを合成するすべてのルートで効きます。ルート単位で抑制したい場合は suppressions ファイル（`npx svelte-vitals --update-suppressions`）を使ってください。`overrides` でルートやパス単位に絞るか、ルールごと無効化するには、次のように設定します。

```js svelte-vitals.config.js
export default {
  rules: {
    'a11y/duplicate-landmark': 'off'
  }
};
```
