開発・個人開発

AstroでSEO・構造化データを自動化する 記事ごとの設定が不要になる実装

AstroでSEO・構造化データを自動化する 記事ごとの設定が不要になる実装

この記事の要点

Astroでは、メタタグ・OGP・Article JSON-LD・FAQPage構造化データ・sitemap・llms.txtを一度実装すれば、記事を追加するだけで自動適用される仕組みを作れる。実装コードと設計の考え方を解説する。

結論

AstroのSEO自動化は、1つのレイアウトコンポーネントに実装すればすべての記事に自動適用される。メタタグ・OGP・Article JSON-LD・FAQPage JSON-LDをまとめたHeadコンポーネントを作り、それを記事レイアウトから呼び出す設計にすることで、記事ごとのSEO設定が不要になる。

基本的な設計方針

Astroの記事レイアウト(src/layouts/ArticleLayout.astro)にSEO設定を集中させる。記事のfrontmatter(title・description・pubDate・faq等)からHeadタグを自動生成する設計だ。

frontmatter(記事ファイル)

ArticleLayout(レイアウトコンポーネント)

SeoHead(SEO用のコンポーネント)
  ├── メタタグ・canonical
  ├── OGP(Open Graph Protocol)
  ├── Article JSON-LD
  └── FAQPage JSON-LD(faqがある場合)

sitemapの自動生成

まず最も簡単な施策から始める。公式プラグインを追加するだけでサイトマップが自動生成される。

npx astro add sitemap

astro.config.mjsを更新する:

import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';

export default defineConfig({
  site: 'https://your-site.com',  // 本番URLを指定
  integrations: [sitemap()],
});

ビルド後に/sitemap-index.xml/sitemap-0.xmlが生成される。Google Search Consoleにこのsitemapを送信することで、インデックス促進ができる。

SEOHeadコンポーネントの実装

src/components/SeoHead.astroを作成する。

---
interface Props {
  title: string;
  description: string;
  pubDate?: Date;
  updatedDate?: Date;
  slug?: string;
  category?: string;
  tags?: string[];
  faq?: Array<{ q: string; a: string }>;
  ogImage?: string;
}

const {
  title,
  description,
  pubDate,
  updatedDate,
  slug,
  category,
  tags = [],
  faq = [],
  ogImage,
} = Astro.props;

const canonicalUrl = new URL(slug ? `/articles/${slug}/` : '/', Astro.site).toString();
const imageUrl = ogImage ?? new URL('/og-default.png', Astro.site).toString();

// Article構造化データ
const articleJsonLd = pubDate ? JSON.stringify({
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": title,
  "description": description,
  "datePublished": pubDate.toISOString(),
  "dateModified": (updatedDate ?? pubDate).toISOString(),
  "author": {
    "@type": "Organization",
    "name": "Meliorra編集部"
  },
  "publisher": {
    "@type": "Organization",
    "name": "Meliorra AI Media",
    "url": Astro.site?.toString()
  },
  "mainEntityOfPage": canonicalUrl,
  "keywords": tags.join(', '),
}) : null;

// FAQPage構造化データ
const faqJsonLd = faq.length > 0 ? JSON.stringify({
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": faq.map(item => ({
    "@type": "Question",
    "name": item.q,
    "acceptedAnswer": {
      "@type": "Answer",
      "text": item.a
    }
  }))
}) : null;
---

<!-- 基本メタタグ -->
<meta name="description" content={description} />
<link rel="canonical" href={canonicalUrl} />

<!-- Open Graph -->
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:url" content={canonicalUrl} />
<meta property="og:type" content={pubDate ? "article" : "website"} />
<meta property="og:image" content={imageUrl} />
<meta property="og:site_name" content="Meliorra AI Media" />
<meta property="og:locale" content="ja_JP" />

<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={imageUrl} />

<!-- Article固有のOGP -->
{pubDate && <meta property="article:published_time" content={pubDate.toISOString()} />}
{updatedDate && <meta property="article:modified_time" content={updatedDate.toISOString()} />}
{category && <meta property="article:section" content={category} />}
{tags.map(tag => <meta property="article:tag" content={tag} />)}

<!-- 構造化データ -->
{articleJsonLd && <script type="application/ld+json" set:html={articleJsonLd} />}
{faqJsonLd && <script type="application/ld+json" set:html={faqJsonLd} />}

ArticleLayoutでの利用

src/layouts/ArticleLayout.astroでSeoHeadを呼び出す。

---
import SeoHead from '../components/SeoHead.astro';
import type { CollectionEntry } from 'astro:content';

interface Props {
  post: CollectionEntry<'articles'>;
}

const { post } = Astro.props;
const { title, description, pubDate, updatedDate, tags, faq, category } = post.data;
---

<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>{title} | Meliorra AI Media</title>
    <SeoHead
      title={title}
      description={description}
      pubDate={pubDate}
      updatedDate={updatedDate}
      slug={post.slug}
      category={category}
      tags={tags}
      faq={faq}
    />
  </head>
  <body>
    <slot />
  </body>
</html>

記事ページ(src/pages/articles/[slug].astro)でこのレイアウトを使うことで、全記事に自動でSEO設定が適用される。

OGP画像の自動生成(応用)

SNSでシェアされたときに表示されるOGP画像を、記事タイトルから動的生成できる。AstroのAPI Routesと@vercel/ogライブラリを組み合わせると実装できる。

GET /api/og.png?title=記事タイトル&category=security
→ タイトルが入ったOGP画像を動的生成して返す

毎回Canvaで画像を作る手間が省けるため、記事量産ワークフローとの相性がよい。

robots.txtでAIクローラーを許可する

public/robots.txtに主要なAIクローラーへのアクセスを明示的に許可する。

User-agent: GPTBot
Allow: /

User-agent: ClaudeBot
Allow: /

User-agent: PerplexityBot
Allow: /

User-agent: Applebot-Extended
Allow: /

User-agent: *
Allow: /

Sitemap: https://your-site.com/sitemap-index.xml

実装の確認方法

構造化データの検証:Google の Rich Results Test(search.google.com/test/rich-results)でページURLを入力すると、構造化データが正しく認識されているかを確認できる。

OGPの確認:Twitter Card Validator(developer.x.com/docs/twitter-for-websites/cards/guides/troubleshooting-cards)や、Facebook Sharing Debuggerでシェア時の表示を確認できる。

metaタグの確認:ブラウザのデベロッパーツールで<head>の内容を確認する。canonicalのURLが正しいか、descriptionが意図した内容かを記事ごとに抜き打ちでチェックする。

llms.txtの実装についてはllms.txtとは?AIに見つかるサイトにする最適化ガイドを、Astroの基本的なセットアップはAstroで爆速ブログ・メディアを作る入門で解説している。

まとめ

AstroのSEO自動化は、SeoHeadコンポーネントを1つ作って記事レイアウトから呼び出す設計で完結する。frontmatterのtitle・description・pubDate・faqを渡すだけで、メタタグ・OGP・Article/FAQPage JSON-LDが全記事に適用される。sitemapは公式プラグインで自動生成できる。一度実装してしまえば、記事を追加するごとにSEO設定が自動で完成する状態になる。

よくある質問

AstroでSEOの設定は難しいですか

基本的なメタタグとsitemapは公式プラグインで数行の設定で完了します。構造化データ(JSON-LD)はコンポーネントとして一度書けば全記事に自動適用できます。難しさより、一度設定すると何もしなくてよくなる恩恵の方が大きいです。

構造化データとは何ですか

Googleなどの検索エンジンやAIがページの内容を理解しやすくするための、機械可読な情報です。Article・BreadcrumbList・FAQPage・HowToなどのタイプがあり、正しく実装するとリッチリザルト(検索結果に星や価格が表示されるもの)や、AIからの引用率向上につながります。

FAQPage構造化データを実装すると何がいいですか

Googleの検索結果でQ&Aが展開表示されるリッチリザルトが表示される可能性があります。また、ChatGPT・Perplexity・Claudeなどの対話型AIが回答を生成するとき、明確なQ&A形式の情報は引用しやすいため、AI経由のトラフィックにも効きます。

sitemapは手動で更新が必要ですか

@astrojs/sitemapを使うと、ビルド時に全ページのURLからsitemapが自動生成されます。記事を追加するたびに自動更新されるため、手動操作は不要です。