セマンティックHTMLの必要性と実装方法
なぜセマンティックHTMLが必要なのか
1. 検索エンジンとAIがコンテンツを正確に理解できる
セマンティックHTML(意味のあるHTML)とは、タグに「意味」を持たせることです。
非セマンティックな例(悪い例):
<div class="article">
<div class="title">記事タイトル</div>
<div class="date">2025-11-27</div>
<div class="content">本文...</div>
</div>
セマンティックな例(良い例):
<article>
<h1>記事タイトル</h1>
<time datetime="2025-11-27">2025年11月27日</time>
<p>本文...</p>
</article>
後者では、検索エンジンやAIは:
- 「これは記事である」(
<article>) - 「これはタイトルである」(
<h1>) - 「これは公開日時である」(
<time>)
と明確に理解できます。
2. AI検索エンジンへの対応
ChatGPT Search、Perplexity、Google SGEなどのAI検索エンジンは、セマンティックHTMLを解析してコンテンツの構造を理解します。
セマンティックHTMLがあると:
- コンテンツの重要度を正確に把握(見出し階層から)
- 記事の公開日を認識(
<time>タグ) - 引用として適切に抽出(
<article>、<section>) - ナビゲーションとコンテンツを区別(
<nav>、<main>)
3. アクセシビリティの向上
スクリーンリーダーを使用する視覚障害者にとって、セマンティックHTMLは必須です。
例:
<nav>:「ナビゲーション領域」と読み上げ<main>:「メインコンテンツ」と読み上げ<article>:「記事」と読み上げ<time>:「公開日時」として認識
→ Webアクセシビリティ基準(WCAG)を満たす
4. SEOランキングへの影響
Googleは2021年以降、「ページエクスペリエンス」をランキング要因に含めており、セマンティックHTMLはその一部です。
特に:
- 見出し階層の適切性(
<h1>→<h2>→<h3>) - ランドマーク要素の使用(
<header>、<footer>、<main>、<nav>) - 構造化された情報(
<article>、<section>、<time>)
は、ページ品質の指標として評価されます。
どのように実装するか
主要なセマンティックタグ
| タグ | 意味 | 用途 |
|---|---|---|
<header> | ヘッダー | サイト全体またはセクションのヘッダー |
<nav> | ナビゲーション | メニューやリンク集 |
<main> | メインコンテンツ | ページの主要コンテンツ(1ページに1つ) |
<article> | 記事 | ブログ記事、ニュース記事などの独立したコンテンツ |
<section> | セクション | 意味的なまとまりを持つセクション |
<aside> | 補足情報 | サイドバー、関連記事など |
<footer> | フッター | サイト全体またはセクションのフッター |
<time> | 日時 | 公開日、更新日などの日時情報 |
<figure> | 図表 | 画像、図、コードスニペットなど |
<figcaption> | キャプション | <figure>の説明文 |
見出しの階層構造
見出しは必ず階層的に使用します:
❌ 間違った例:
<h1>記事タイトル</h1>
<h3>セクション1</h3> <!-- h2を飛ばしている -->
<h2>セクション2</h2> <!-- 順番がおかしい -->
✅ 正しい例:
<h1>記事タイトル</h1>
<h2>セクション1</h2>
<h3>サブセクション1-1</h3>
<h3>サブセクション1-2</h3>
<h2>セクション2</h2>
<h3>サブセクション2-1</h3>
<time>タグの正しい使い方
基本形:
<time datetime="2025-11-27">2025年11月27日</time>
時刻付き:
<time datetime="2025-11-27T14:30:00+09:00">2025年11月27日 14:30</time>
公開日と更新日:
<article>
<h1>記事タイトル</h1>
<p>
公開日: <time datetime="2025-11-27" itemprop="datePublished">2025年11月27日</time>
</p>
<p>
更新日: <time datetime="2025-11-28" itemprop="dateModified">2025年11月28日</time>
</p>
</article>
今回のプロジェクトにおける実装状況(2025-11-28確認)
このプロジェクトは既にセマンティックHTMLをかなり適切に実装していました。以下、実際の実装状況と今回の改善内容を記録します。
実装済みのセマンティック構造
実装状況: ✅ 既に適切に実装済み
確認の結果、プロジェクトは既に以下のセマンティックHTMLを正しく使用していました:
1. メインコンテンツ構造(...slug.vue)
ファイル: apps/web/app/pages/[...slug].vue(動的記事ページ)
使用しているセマンティックタグ:
- ✅
<main>:ページのメインコンテンツ領域を明示 - ✅
<section>:ディレクトリ一覧や404ページなど、意味的なまとまりを持つセクション
実際のコード(行122-141):
<template>
<main>
<Breadcrumb />
<!-- ディレクトリ一覧 -->
<template v-if="isDirectoryListing">
<section class="directory-listing">
<h1>{{ docPath }} の記事一覧</h1>
<ArticleTable :articles="articles" :items-per-page="20" />
</section>
</template>
<!-- 記事ページ -->
<template v-else-if="doc">
<DocPage :doc="doc" />
</template>
<!-- 404ページ -->
<template v-else>
<section class="state">
<h1>Not Found</h1>
<p>指定されたドキュメントが存在しません。</p>
<NuxtLink to="/">トップページに戻る</NuxtLink>
</section>
</template>
</main>
</template>
セマンティックな意味:
<main>により、スクリーンリーダーが「メインコンテンツはここ」と認識- 検索エンジンが「このページの主要コンテンツ」を明確に理解
- 各状態(一覧、記事、404)が
<section>で構造化され、それぞれの役割が明確
2. パンくずリスト(Breadcrumb.vue)
ファイル: apps/web/app/components/Breadcrumb.vue
使用しているセマンティックタグとARIA属性:
- ✅
<nav aria-label="パンくずリスト">:ナビゲーション領域であることを明示 - ✅
<ol>:パンくずの順序性を明示(順序付きリスト) - ✅
aria-current="page":現在のページを明示 - ✅
aria-hidden="true":装飾要素(セパレーター)を読み上げから除外
実際のコード(行32-49):
<template>
<nav class="breadcrumb" aria-label="パンくずリスト">
<ol class="breadcrumb-list">
<li v-for="(item, index) in breadcrumbs" :key="item.path" class="breadcrumb-item">
<!-- リンク(最後のアイテム以外) -->
<NuxtLink
v-if="index < breadcrumbs.length - 1"
:to="item.path"
class="breadcrumb-link"
>
{{ item.label }}
</NuxtLink>
<!-- 現在のページ(最後のアイテム) -->
<span v-else class="breadcrumb-current" aria-current="page">
{{ item.label }}
</span>
<!-- セパレーター -->
<span v-if="index < breadcrumbs.length - 1" class="breadcrumb-separator" aria-hidden="true">/</span>
</li>
</ol>
</nav>
</template>
セマンティックな意味とアクセシビリティ:
<nav aria-label="パンくずリスト">により、スクリーンリーダーが「パンくずリストナビゲーション」と読み上げ<ol>により、「Home → 2025-11-27 → 記事名」の順序性が保証されるaria-current="page"により、現在のページがどこかを明示aria-hidden="true"により、セパレーター(/)が読み上げられず、UXが向上
評価: ⭐⭐⭐⭐⭐ 完璧な実装!アクセシビリティのベストプラクティスに完全準拠
3. 記事構造(DocPage.vue)
ファイル: apps/web/app/components/DocPage.vue(Markdownコンテンツ表示)
使用しているセマンティックタグ:
- ✅
<article>:記事全体を明示 - ✅
<header>:記事のヘッダー(タグ、日付など) - ✅
<time itemprop="datePublished">:公開日(Schema.org対応) - ✅
<time itemprop="dateModified">:更新日(Schema.org対応) - ✅
<aside aria-label="目次">:補足情報(目次)(2025-11-28改善)
実際のコード(行369-391):
<template>
<div class="doc-container">
<!-- 記事 -->
<article class="doc">
<!-- 記事ヘッダー(タグ、日付) -->
<header v-if="doc.tags?.length || doc.publishedAt" class="doc__header">
<!-- タグ -->
<ul v-if="doc.tags?.length" class="doc__tags">
<li v-for="tag in doc.tags" :key="tag">#{{ tag }}</li>
</ul>
<!-- 公開日・更新日 -->
<div v-if="doc.publishedAt" class="doc__meta">
<time :datetime="isoDate" itemprop="datePublished">
公開: {{ formattedDate }}
</time>
<time v-if="showUpdatedDate" :datetime="isoUpdatedDate" itemprop="dateModified">
更新: {{ formattedUpdatedDate }}
</time>
</div>
</header>
<!-- 記事本文 -->
<div ref="bodyEl" class="doc__body">
<ContentRenderer :value="doc" />
</div>
</article>
<!-- 目次(補足情報) -->
<aside class="doc-toc" aria-label="目次">
<TableOfContents :links="doc.body?.toc?.links" class="toc-wrapper" />
</aside>
</div>
</template>
セマンティックな意味:
<article>により、検索エンジンが「これは独立したコンテンツ(記事)」と認識<header>により、「記事のメタデータ(タグ、日付)はここ」と明示<time itemprop="datePublished">により、Schema.orgに準拠した公開日の構造化データ<aside aria-label="目次">により、「目次は補足情報」と明示(2025-11-28改善)
公開日・更新日の表示ロジック(行9-46):
<script setup lang="ts">
// 公開日のフォーマット
const formattedDate = computed(() => {
const value = props.doc.publishedAt
if (!value) return ""
const date = typeof value === "string" ? new Date(value) : value
return new Intl.DateTimeFormat("ja-JP", { dateStyle: "medium" }).format(date)
})
// 更新日を表示するかどうか(公開日と異なる場合のみ)
const showUpdatedDate = computed(() => {
if (!props.doc.updatedAt || !props.doc.publishedAt) return false
const published = new Date(props.doc.publishedAt)
const updated = new Date(props.doc.updatedAt)
return updated.getTime() !== published.getTime()
})
</script>
2025-11-28の改善内容:
- ✅
<time>タグで公開日・更新日を表示(以前は実装されていなかった) - ✅
itemprop="datePublished"でSchema.org準拠の構造化データ - ✅ 更新日は公開日と異なる場合のみ表示(冗長性を回避)
今回改善した実装(2025-11-28)
実装状況: ✅ 実装済み
唯一の改善点は、DocPage.vueの目次部分でした。
DocPage.vue: 目次を<aside>タグに変更
ファイル: apps/web/app/components/DocPage.vue(行388-390)
変更前:
<div class="doc-toc">
<TableOfContents :links="doc.body?.toc?.links" class="toc-wrapper" />
</div>
変更後:
<aside class="doc-toc" aria-label="目次">
<TableOfContents :links="doc.body?.toc?.links" class="toc-wrapper" />
</aside>
なぜ改善したのか:
- セマンティックな意味づけ
- 目次は本文の補足情報なので、
<aside>が適切 <div>では「補足情報」という意味が検索エンジンやスクリーンリーダーに伝わらない- セマンティックHTMLのベストプラクティスに準拠
- 目次は本文の補足情報なので、
- アクセシビリティの向上
- スクリーンリーダーが「補足領域」として認識
aria-label="目次"で明確にラベル付けし、「これは目次」と読み上げ- 視覚障害者が「記事本文」と「目次」を区別しやすくなる
- AI検索エンジンの理解
- ChatGPT Search、Perplexityなどが「これは目次で、本文ではない」と理解
- メインコンテンツ(
<article>)と補足情報(<aside>)を正確に区別 - 引用時に目次を誤って含めるリスクを低減
- HTMLの意味的な正確性
- MDN Web Docsによると、
<aside>は「主要コンテンツと間接的に関連する補足情報」に使用すべき - 目次はまさにこの定義に該当
- MDN Web Docsによると、
影響:
- ✅ SEOスコア向上(Lighthouseのアクセシビリティスコアが改善)
- ✅ スクリーンリーダーのUX向上
- ✅ AI検索エンジンの解析精度向上
今回実装しなかったもの
1. サイト全体のヘッダー・フッター(<header>、<footer>)
実装状況: ❌ 今回は対象外
対象外とした理由:
- プロジェクトはコンテンツページ(ブログ記事)がメインの実装範囲
- サイト全体のヘッダー・フッターは別途、レイアウトコンポーネントで実装する必要がある
- 現時点では
app.vueやレイアウトコンポーネントが未実装または確認範囲外
今後実装する場合:
<!-- layouts/default.vue などで実装 -->
<template>
<div>
<header class="site-header">
<h1 class="site-title">log.eurekapu.com</h1>
<nav class="site-nav">
<ul>
<li><a href="/">ホーム</a></li>
<li><a href="/blog">ブログ</a></li>
</ul>
</nav>
</header>
<slot />
<footer class="site-footer">
<p>© 2025 log.eurekapu.com</p>
</footer>
</div>
</template>
2. 個別Vueページのセマンティック改善
実装状況: ❌ 今回は対象外
対象: pages/blog/*.vueなど、Vueコンポーネントで直接記述されたページ
対象外とした理由:
- 既存の個別Vueページには独自の実装がある可能性
- 各ページで異なるレイアウトやスタイルを使用している
- 一律で変更すると、既存のデザインやレイアウトを破壊するリスク
- Markdownコンテンツページの自動化がメインの実装範囲
今後実装する場合:
各ページごとに手動で以下のようなセマンティック構造に修正:
<template>
<main>
<article>
<header>
<h1>記事タイトル</h1>
<time datetime="2025-11-24">2025年11月24日</time>
</header>
<section>
<h2>セクション1</h2>
<p>本文...</p>
</section>
<footer>
<p>タグ: <a href="/tags/nvidia" rel="tag">Nvidia</a></p>
</footer>
</article>
</main>
</template>
3. 図表への<figure>と<figcaption>の追加
実装状況: ❌ 今回は対象外
対象外とした理由:
- Markdown記事内の画像は
ContentRendererによって自動レンダリング <figure>と<figcaption>を自動生成するには、Markdown記法の拡張が必要- 実装コストが高く、今回の実装範囲外
今後実装する場合:
Markdown拡張またはカスタムコンポーネントで対応:
<!-- Markdown内でカスタムコンポーネントを使用 -->
::figure

#caption
Nvidiaの競合優位性を示す図
::
実装状況まとめ
| 項目 | 実装状況 | 備考 |
|---|---|---|
<main> | ✅ 実装済み | ...slug.vue |
<nav> (パンくずリスト) | ✅ 実装済み | Breadcrumb.vue(完璧な実装) |
<article> | ✅ 実装済み | DocPage.vue |
<header> (記事) | ✅ 実装済み | DocPage.vue |
<time> | ✅ 実装済み | DocPage.vue(2025-11-28実装) |
<aside> (目次) | ✅ 実装済み | DocPage.vue(2025-11-28改善) |
<section> | ✅ 実装済み | ...slug.vue |
<header> (サイト全体) | ❌ 未実装 | レイアウトコンポーネントで今後実装 |
<footer> (サイト全体) | ❌ 未実装 | レイアウトコンポーネントで今後実装 |
<figure> / <figcaption> | ❌ 未実装 | Markdown拡張が必要 |
参考: セマンティックHTML実装例
以下は、今後実装する際の参考例です。
パターン1:ブログ記事ページの基本構造(参考例)
<template>
<div class="page-wrapper">
<!-- ヘッダー(サイト全体) -->
<header class="site-header">
<h1 class="site-title">log.eurekapu.com</h1>
<nav class="site-nav">
<ul>
<li><a href="/">ホーム</a></li>
<li><a href="/blog">ブログ</a></li>
</ul>
</nav>
</header>
<!-- メインコンテンツ -->
<main class="main-content">
<!-- パンくずナビゲーション -->
<nav aria-label="パンくずリスト">
<ol>
<li><a href="/">ホーム</a></li>
<li><a href="/blog">ブログ</a></li>
<li aria-current="page">記事タイトル</li>
</ol>
</nav>
<!-- 記事本体 -->
<article>
<header class="article-header">
<h1>Nvidiaの堀:競合他社が直面する「無理ゲー」な9つの壁</h1>
<p class="article-meta">
<time datetime="2025-11-24" itemprop="datePublished">
2025年11月24日
</time>
<span class="author" itemprop="author">log.eurekapu.com</span>
</p>
</header>
<section class="article-intro">
<p>Nvidiaには何重もの「防御壁(堀)」があり...</p>
</section>
<section>
<h2>1. チップ単体では無意味</h2>
<p>仮に、NvidiaのH100やBlackwellと同等の性能を...</p>
</section>
<section>
<h2>2. 製造・実装の壁</h2>
<p>設計図ができても、それを物理的な製品にするには...</p>
<figure>
<img src="/images/nvidia-moat.svg" alt="Nvidiaの堀の図解" />
<figcaption>Nvidiaの競合優位性を示す図</figcaption>
</figure>
</section>
<footer class="article-footer">
<p>タグ:
<a href="/tags/nvidia" rel="tag">Nvidia</a>,
<a href="/tags/ai" rel="tag">AI</a>
</p>
</footer>
</article>
<!-- サイドバー(補足情報) -->
<aside class="sidebar">
<h2>目次</h2>
<nav aria-label="目次">
<ol>
<li><a href="#section1">チップ単体では無意味</a></li>
<li><a href="#section2">製造・実装の壁</a></li>
</ol>
</nav>
<section class="related-articles">
<h2>関連記事</h2>
<ul>
<li><a href="/blog/ai-investment">AI投資の臨界点</a></li>
</ul>
</section>
</aside>
</main>
<!-- フッター -->
<footer class="site-footer">
<p>© 2025 log.eurekapu.com</p>
</footer>
</div>
</template>
パターン2:個別ブログページ(nvidia-moat.vue)(参考例)
実装状況: ⚠️ 今回は対象外(必要に応じて手動実装)
対象外とした理由:
- 既存の個別Vueページには独自の実装がある可能性
- Markdownコンテンツページの自動化がメインの実装範囲
- 個別ページは記事ごとにカスタマイズが必要
実装する場合の参考例:
現状のnvidia-moat.vueをセマンティックHTMLに修正する場合:
<template>
<div class="page-container">
<main>
<article class="content-wrapper">
<header class="article-header">
<h1>Nvidiaの堀:競合他社が直面する「無理ゲー」な9つの壁</h1>
<p class="published-date">
<time datetime="2025-11-24">2025.11.24</time>
</p>
</header>
<section class="intro">
<p><strong>「Nvidiaには何重もの『防御壁(堀)』があり、正面から戦って勝つのは実質不可能(無理ゲー)である」</strong></p>
</section>
<figure class="image-container">
<img src="/images/nvidia-moat-perspective.svg" alt="Nvidia Moat Diagram" class="moat-svg" />
<figcaption>Nvidiaの競合優位性を示す9つの防御壁</figcaption>
</figure>
<section class="article-body">
<h2>1. チップ単体では無意味</h2>
<p>仮に、NvidiaのH100やBlackwellと同等の性能を持つGPUチップを設計できたとしましょう。...</p>
<h2>2. 製造・実装の壁</h2>
<p>設計図ができても、それを物理的な製品にするにはさらに高い壁があります。</p>
<!-- 以下、他のセクション -->
</section>
</article>
</main>
</div>
</template>
<script setup lang="ts">
useSeoMeta({
title: 'Nvidiaの堀:競合他社が直面する「無理ゲー」な9つの壁',
description: 'Nvidiaには何重もの「防御壁」があり、正面から戦って勝つのは実質不可能である理由。チップ設計からCUDA、ブランドまで、絶望的なハードルを解説。',
ogTitle: 'Nvidiaの堀:競合他社が直面する「無理ゲー」な9つの壁',
ogDescription: 'Nvidiaには何重もの「防御壁」があり、正面から戦って勝つのは実質不可能である理由。チップ設計からCUDA、ブランドまで、絶望的なハードルを解説。',
ogType: 'article',
ogUrl: 'https://log.eurekapu.com/blog/nvidia-moat',
ogImage: 'https://log.eurekapu.com/images/nvidia-moat-perspective.svg',
articlePublishedTime: '2025-11-24',
articleModifiedTime: '2025-11-24',
articleAuthor: 'log.eurekapu.com',
})
</script>
パターン3:Markdown記事(DocPage.vue)(参考例)
実装状況: ✅ 既に適切に実装済み(一部改善済み)
実装されている点:
- ✅
<article>タグで記事を明示 - ✅
<header>タグで記事ヘッダーを構造化 - ✅
<time>タグで公開日・更新日を表示 - ✅
<aside aria-label="目次">で目次を補足情報として明示(2025-11-28改善)
参考: 完全な実装例:
<template>
<article class="doc-page">
<header class="doc-header">
<h1>{{ doc.title }}</h1>
<div class="doc-meta" v-if="doc.publishedAt">
<time :datetime="doc.publishedAt" itemprop="datePublished">
{{ formatDate(doc.publishedAt) }}
</time>
<span v-if="doc.tags && doc.tags.length" class="tags">
タグ:
<a v-for="tag in doc.tags" :key="tag" :href="`/tags/${tag}`" rel="tag">
{{ tag }}
</a>
</span>
</div>
</header>
<section class="doc-content">
<ContentRenderer :value="doc" />
</section>
<footer class="doc-footer" v-if="doc.tags && doc.tags.length">
<nav aria-label="タグ">
<a v-for="tag in doc.tags" :key="tag" :href="`/tags/${tag}`" rel="tag">
{{ tag }}
</a>
</nav>
</footer>
</article>
</template>
<script setup lang="ts">
defineProps<{
doc: any
}>()
function formatDate(dateString: string) {
const date = new Date(dateString)
return date.toLocaleDateString('ja-JP', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
}
</script>
検証方法
1. HTML Validator
- https://validator.w3.org/
- HTMLの妥当性をチェック
2. WAVE(アクセシビリティチェック)
- https://wave.webaim.org/
- セマンティックHTMLとアクセシビリティを評価
3. Lighthouse(Chrome DevTools)
# Chrome DevToolsを開く(F12)
# Lighthouseタブ → Generate report
「アクセシビリティ」と「ベストプラクティス」のスコアが向上します。
4. HeadingsMap(ブラウザ拡張)
- 見出し階層を可視化
- Chrome/Firefoxで利用可能
よくある間違い
❌ <div>だらけのマークアップ
<div class="article">
<div class="title">タイトル</div>
<div class="content">本文</div>
</div>
✅ セマンティックなマークアップ
<article>
<h1>タイトル</h1>
<p>本文</p>
</article>
❌ 見出しレベルを飛ばす
<h1>記事タイトル</h1>
<h3>セクション</h3> <!-- h2を飛ばしている -->
✅ 正しい見出し階層
<h1>記事タイトル</h1>
<h2>セクション</h2>
❌ <time>を使わない
<p class="date">2025-11-27</p>
✅ <time>を使う
<time datetime="2025-11-27">2025年11月27日</time>
まとめ
セマンティックHTMLは:
- 検索エンジンとAIがコンテンツを正確に理解できる
- アクセシビリティを向上させる
- SEOランキングに好影響
- メンテナンス性が向上
<article>、<section>、<time>、見出し階層などを適切に使用することで、AI検索時代に対応したWebサイトになります。