• #SEO
  • #メタデータ
  • #公開日
  • #更新日
  • #AI検索
未分類

公開日・更新日メタデータの必要性と実装方法

なぜ公開日・更新日メタデータが必要なのか

1. コンテンツの鮮度を示す

検索エンジンとユーザーは、情報の新しさを重視します。特に:

  • ニュース記事
  • 技術記事(フレームワークのバージョン情報など)
  • 市況分析(株式、経済データ)
  • ハウツー記事(最新手法)

これらは、古い情報が無価値または有害になる可能性があります。

公開日・更新日を明示することで:

  • ユーザーが情報の鮮度を判断できる
  • 検索エンジンが最新コンテンツを優先的にランク付け
  • AI検索が引用時に「この情報は2025年11月時点のもの」と注釈

2. Googleの「フレッシュネス」アルゴリズム

Googleは2011年から「Freshness Update」を導入しており、特定のクエリに対して新しいコンテンツを優先します。

フレッシュネスが重視されるクエリ:

  • 時事ニュース(「○○社の最新決算」)
  • 定期的に変わる情報(「2025年の確定申告方法」)
  • 頻繁に更新される話題(「ChatGPT 使い方」)

公開日・更新日がないと、コンテンツの鮮度をGoogleは正確に判断できず、ランキングで不利になります。

3. AI検索エンジンでの引用精度

ChatGPT Search、Perplexity、Google SGEなどは、情報の信頼性を判断する際に公開日・更新日を重視します。

例:

ユーザー: 「NvidiaのH100の性能は?」
AI: 「2025年11月の情報によると、H100は... (出典: log.eurekapu.com, 2025年11月24日公開)」

日付がないと:

  • 引用されにくい
  • 古い情報と判断される
  • 信頼性が低下

4. リッチスニペットに表示される

Googleの検索結果で、記事の公開日が表示される「リッチスニペット」を得られます:

Nvidiaの堀:競合他社が直面する「無理ゲー」な9つの壁
log.eurekapu.com › blog › nvidia-moat
2025年11月24日 — Nvidiaには何重もの「防御壁」があり、正面から戦って...

クリック率(CTR)が向上

5. ユーザー体験の向上

ユーザーは「この情報は今でも有効か?」を気にします。

公開日・更新日が明示されていると:

  • 安心して情報を利用できる
  • 古い情報を誤って使うリスクが減る
  • サイトの信頼性が向上

どのように実装するか

必要なメタデータ

1. HTMLメタタグ(OGP)

<meta property="article:published_time" content="2025-11-27T00:00:00+09:00" />
<meta property="article:modified_time" content="2025-11-27T14:30:00+09:00" />

2. JSON-LD構造化データ

{
  "@context": "https://schema.org",
  "@type": "Article",
  "datePublished": "2025-11-27",
  "dateModified": "2025-11-27"
}

3. HTMLタグ(セマンティック)

<time datetime="2025-11-27" itemprop="datePublished">2025年11月27日</time>

ISO 8601形式の日時

公開日・更新日はISO 8601形式で指定します:

日付のみ:

2025-11-27

日時(タイムゾーン付き):

2025-11-27T14:30:00+09:00
  • T:日付と時刻の区切り
  • +09:00:日本標準時(JST)のオフセット

今回のプロジェクトにおける実装

パターン1:Markdown記事のフロントマター

content/2025-11-27/article.mdの例:

---
title: 記事タイトル
description: 記事の説明
publishedAt: 2025-11-27
tags: [SEO, Nuxt]
---

# 記事本文

...

パターン2:動的記事ページ(...slug.vue)

実装状況: ✅ 実装済み(2025-11-28)

ファイル: apps/web/app/pages/[...slug].vue

Markdownコンテンツを動的に表示するページで、OGPメタデータとJSON-LD構造化データを自動生成します。

実装内容

基本的なOGP・JSON-LD構造化データは既に実装済みでしたが、更新日(updatedAt)のサポートを追加しました。

更新日対応を追加した理由:

  • 記事を更新した際に、更新日を明示することでSEO評価が向上
  • AI検索エンジンが「最新の更新は○○日」と認識できる
  • ユーザーが「この情報は最近更新されている」と判断可能

コード例

<script setup lang="ts">
import { computed } from "vue"
import { queryCollection } from "#imports"

const route = useRoute()
const docPath = computed(() => {
  const normalized = route.path.replace(/\/$/, "")
  return normalized === "" ? "/" : normalized
})

const { data: doc } = await useAsyncData(
  `content-${docPath.value}`,
  () => queryCollection("pages").path(docPath.value).first(),
  { watch: [docPath] }
)

// OGPメタデータ
useSeoMeta({
  title: () => doc.value?.title || 'ページが見つかりません',
  description: () => doc.value?.description || '',

  // 公開日・更新日
  articlePublishedTime: () => doc.value?.publishedAt || '',
  // ✅ updatedAtがあればそれを使用、なければpublishedAtを使用
  articleModifiedTime: () => doc.value?.updatedAt || doc.value?.publishedAt || '',
  articleAuthor: 'log.eurekapu.com',

  // OGP
  ogType: 'article',
  ogUrl: () => `https://log.eurekapu.com${docPath.value}`,
  ogImage: 'https://log.eurekapu.com/favicon.svg',
})

// JSON-LD構造化データ
useHead({
  script: computed(() => {
    if (!doc.value) return []

    return [
      {
        type: 'application/ld+json',
        children: JSON.stringify({
          '@context': 'https://schema.org',
          '@type': 'Article',
          headline: doc.value.title || 'Untitled',
          description: doc.value.description || '',
          datePublished: doc.value.publishedAt || '',
          // ✅ updatedAtがあればそれを使用、なければpublishedAtを使用
          dateModified: doc.value.updatedAt || doc.value.publishedAt || '',
          author: {
            '@type': 'Person',
            name: 'log.eurekapu.com'
          },
          publisher: {
            '@type': 'Organization',
            name: 'log.eurekapu.com',
            logo: {
              '@type': 'ImageObject',
              url: 'https://log.eurekapu.com/favicon.svg'
            }
          },
          image: 'https://log.eurekapu.com/favicon.svg',
          url: `https://log.eurekapu.com${docPath.value}`,
        })
      }
    ]
  })
})
</script>

<template>
  <main>
    <template v-if="doc">
      <DocPage :doc="doc" />
    </template>
  </main>
</template>

スキーマ定義の追加

content.config.tsupdatedAtフィールドを追加:

// apps/web/content.config.ts
schema: z.object({
  title: z.string().optional(),
  description: z.string().optional(),
  path: z.string().optional(),
  tags: z.array(z.string()).optional(),
  publishedAt: z.coerce.date().optional(),
  updatedAt: z.coerce.date().optional() // ✅ 追加
})

パターン3:個別ブログページ(Vue)

実装状況: ⚠️ 今回は対象外(手動実装を推奨)

対象: pages/blog/*.vue

個別のVueコンポーネントで作成されたブログページ(Markdownではなく、直接Vueで記述)です。

今回実装しなかった理由

  1. 既存実装との整合性
    • 既存の個別Vueページには独自のメタデータ実装がある可能性
    • 一律で自動化すると既存の設定を上書きするリスク
  2. 実装範囲の明確化
    • 今回の実装はMarkdownコンテンツページの自動化がメイン
    • Vueページは手動で必要に応じて実装する方が柔軟
  3. ページごとのカスタマイズ性
    • Vueページは独自のOGP画像やメタデータを持つことが多い
    • 自動化より手動実装の方が適切

実装する場合のコード例

必要に応じて、以下のパターンで手動実装してください:

<script setup lang="ts">
const publishedDate = '2025-11-24'
const updatedDate = '2025-11-25' // オプション:更新した場合のみ

useSeoMeta({
  title: 'Nvidiaの堀:競合他社が直面する「無理ゲー」な9つの壁',
  description: 'Nvidiaには何重もの「防御壁」があり、正面から戦って勝つのは実質不可能である理由。',

  // 公開日・更新日
  articlePublishedTime: publishedDate,
  articleModifiedTime: updatedDate || publishedDate, // 更新日があればそれを使用
  articleAuthor: 'log.eurekapu.com',

  // OGP
  ogType: 'article',
  ogUrl: 'https://log.eurekapu.com/blog/nvidia-moat',
  ogImage: 'https://log.eurekapu.com/images/nvidia-moat-perspective.svg',
})

useHead({
  script: [
    {
      type: 'application/ld+json',
      children: JSON.stringify({
        '@context': 'https://schema.org',
        '@type': 'Article',
        headline: 'Nvidiaの堀:競合他社が直面する「無理ゲー」な9つの壁',
        description: 'Nvidiaには何重もの「防御壁」があり、正面から戦って勝つのは実質不可能である理由。',
        datePublished: publishedDate,
        dateModified: updatedDate || publishedDate, // 更新日があればそれを使用
        author: {
          '@type': 'Person',
          name: 'log.eurekapu.com'
        },
        publisher: {
          '@type': 'Organization',
          name: 'log.eurekapu.com',
          logo: {
            '@type': 'ImageObject',
            url: 'https://log.eurekapu.com/favicon.svg'
          }
        },
        image: 'https://log.eurekapu.com/images/nvidia-moat-perspective.svg',
        url: 'https://log.eurekapu.com/blog/nvidia-moat',
      })
    }
  ]
})
</script>

<template>
  <div class="page-container">
    <main>
      <article class="content-wrapper">
        <header class="article-header">
          <h1>Nvidiaの堀:競合他社が直面する「無理ゲー」な9つの壁</h1>
          <p class="published-date">
            <time :datetime="publishedDate">2025.11.24</time>
            <time v-if="updatedDate" :datetime="updatedDate">(更新: 2025.11.25)</time>
          </p>
        </header>

        <!-- 記事本文 -->
      </article>
    </main>
  </div>
</template>

パターン4:DocPageコンポーネントで公開日を表示

実装状況: ✅ 実装済み(2025-11-28)

ファイル: apps/web/app/components/DocPage.vue

Markdownコンテンツを表示するコンポーネントで、記事の公開日・更新日をユーザーに視覚的に表示します。

実装した理由

  1. ユーザー体験の向上
    • メタデータだけでなく、記事内に日付を表示することで、ユーザーが情報の鮮度を即座に判断可能
    • 「この情報は最近のものか?」をページを開いた瞬間に確認できる
  2. SEOの観点
    • <time itemprop="datePublished">タグは検索エンジンが記事本体内で日付を認識
    • OGPやJSON-LDだけでなく、HTMLセマンティクスとしても重要
  3. パンくずリストとの役割分担
    • パンくずリスト: ディレクトリ構造のナビゲーション(例: 2025-11-27
    • DocPage内の日付: 記事メタデータとして、より明確に表示(例: "公開: 2025年11月27日")
    • 重複を許容することで、両方の役割を果たす

実装内容

追加した機能:

  • 公開日の表示(日本語フォーマット: "2025年11月27日")
  • 更新日の条件付き表示(公開日と異なる場合のみ)
  • セマンティックHTML(<time>タグ、itemprop属性)

コード例:

<script setup lang="ts">
import { computed } from "vue"

const props = defineProps<{ doc: ParsedContent }>()

// 公開日のフォーマット
const formattedDate = computed(() => {
  const value = props.doc.publishedAt
  if (!value) return ""
  const date = typeof value === "string" ? new Date(value) : value
  if (Number.isNaN(date.getTime())) return ""
  return new Intl.DateTimeFormat("ja-JP", { dateStyle: "medium" }).format(date)
})

const isoDate = computed(() => {
  const value = props.doc.publishedAt
  if (!value) return ""
  const date = typeof value === "string" ? new Date(value) : value
  if (Number.isNaN(date.getTime())) return ""
  return date.toISOString()
})

// 更新日のフォーマット
const formattedUpdatedDate = computed(() => {
  const value = props.doc.updatedAt
  if (!value) return ""
  const date = typeof value === "string" ? new Date(value) : value
  if (Number.isNaN(date.getTime())) return ""
  return new Intl.DateTimeFormat("ja-JP", { dateStyle: "medium" }).format(date)
})

const isoUpdatedDate = computed(() => {
  const value = props.doc.updatedAt
  if (!value) return ""
  const date = typeof value === "string" ? new Date(value) : value
  if (Number.isNaN(date.getTime())) return ""
  return date.toISOString()
})

// 更新日を表示するかどうか(公開日と異なる場合のみ)
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>

<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>
    <div class="doc-toc">
      <TableOfContents :links="doc.body?.toc?.links" class="toc-wrapper" />
    </div>
  </div>
</template>

<style scoped>
.doc__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  color: #6b7280;
  font-size: 0.875rem;
  margin-top: 0.5rem;
}

.doc__meta time {
  font-weight: 500;
}
</style>

フロントマターの書き方

---
title: 記事タイトル
description: 記事の説明
publishedAt: 2025-11-27
updatedAt: 2025-11-28  # オプション:更新した場合のみ指定
tags: [SEO, Nuxt]
---

表示結果:

  • publishedAtのみ指定: 「公開: 2025年11月27日」
  • updatedAtも指定(かつ異なる日付): 「公開: 2025年11月27日 更新: 2025年11月28日」

検証方法

1. Google Rich Results Test

2. Schema Markup Validator

3. Google Search Console

  • 「拡張機能」→「リッチリザルト」
  • エラーと警告を確認

4. ブラウザDevTools

// OGPメタタグを確認
document.querySelector('meta[property="article:published_time"]')?.content

// JSON-LDを確認
JSON.parse(document.querySelector('script[type="application/ld+json"]')?.textContent)

よくある間違い

❌ 日付形式が不正

<meta property="article:published_time" content="2025/11/27" />

✅ ISO 8601形式を使用

<meta property="article:published_time" content="2025-11-27" />

❌ 公開日が欠落

useSeoMeta({
  title: '記事タイトル',
  description: '説明',
  // articlePublishedTime がない
})

✅ 公開日を必ず設定

useSeoMeta({
  title: '記事タイトル',
  description: '説明',
  articlePublishedTime: '2025-11-27',
})

❌ HTMLに日付が表示されていない

<article>
  <h1>記事タイトル</h1>
  <p>本文...</p>
  <!-- ユーザーが公開日を見られない -->
</article>

✅ HTMLに日付を表示

<article>
  <h1>記事タイトル</h1>
  <time datetime="2025-11-27">2025年11月27日</time>
  <p>本文...</p>
</article>

まとめ

公開日・更新日メタデータは:

  • コンテンツの鮮度を検索エンジンとユーザーに伝える
  • Googleのフレッシュネスアルゴリズムで有利
  • AI検索での引用精度を向上
  • リッチスニペットでCTRを向上

MarkdownのフロントマターpublishedAtを設定し、OGP・JSON-LD・HTMLの3箇所に反映することが重要です。