Webaff

公開 更新 文責 Webaff 編集部

AstroブログのSEO初期設定とsitemapの落とし穴

  • Astro
  • SEO
  • 運用

静的サイトの初期SEOでやることは限られています。ただし Astro の場合、公式インテグレーションを入れただけでは /sitemap.xml が存在しないという分かりにくい挙動があります。この記事では当サイトで実際に動いている設定を、つまずいた箇所つきで残します。

この記事で扱う範囲

対象は Astro + Markdown の静的ブログで、@astrojs/sitemap と @astrojs/netlify を使う構成です。外部施策やキーワード調査は扱いません。扱うのは、公開前に一度設定すればしばらく触らなくてよい配管の部分だけです。

最初に必要な5点

項目 役割
title / description 検索結果の見出しと説明文の材料
sitemap クロールしてほしい URL の一覧
robots.txt クロールしてよい範囲の案内
OGP画像 SNSで共有されたときのカード
構造化データ(JSON-LD) 記事・パンくずなどの機械可読な補助情報

これ以外の施策は、記事と計測が回り始めてからで間に合います。土台がないまま小手先を足すと、効果の切り分けができません。

sitemap は「sitemap.xml」で出てこない

@astrojs/sitemap を入れてビルドすると、出力されるのは sitemap-index.xml と sitemap-0.xml です。sitemap.xml は生成されません。

Search Console に /sitemap.xml を送信する前提で進めると、ここで 404 になります。当サイトは、ビルド完了後に index を sitemap.xml へ複製する小さなフックを足して解決しました。

/** @astrojs/sitemap は sitemap-index.xml を出すため、公開用に sitemap.xml を複製する */
function sitemapXmlAlias() {
  return {
    name: 'sitemap-xml-alias',
    hooks: {
      'astro:build:done': async ({ dir, logger }) => {
        const outDir = fileURLToPath(dir);
        await copyFile(join(outDir, 'sitemap-index.xml'), join(outDir, 'sitemap.xml'));
        logger.info('Copied sitemap-index.xml → sitemap.xml');
      },
    },
  };
}

これを integrations の sitemap() よりあとに置きます。順序を逆にすると複製元がまだ存在せず、ビルドが落ちます。

integrations: [
  sitemap({
    filter: (page) => !page.includes('/lab') && !page.includes('/contact/success/'),
    namespaces: { news: false, xhtml: false, image: false, video: false },
  }),
  // sitemap() のあとに置き、生成後の index を sitemap.xml へ複製する
  sitemapXmlAlias(),
],

sitemap.xml を作らず、Search Console に sitemap-index.xml をそのまま送信する選択でも問題ありません。どちらでもよいので、送信する URL と実際に存在する URL を一致させることだけ確認してください。

載せたくないページを除外する

filter で管理用ページとフォーム送信後のサンクスページを外しています。当サイトでは運営者用ダッシュボード /lab と /contact/success/ が対象です。

namespaces は生成される XML から使っていない拡張名前空間を落とすオプションです。画像や動画のサイトマップ拡張を使わないなら、切っておくと XML が軽くなります。

robots.txt はエンドポイントで書くと URL がずれない

public/robots.txt に直接置くと、ドメインを変えたときに sitemap の URL を直し忘れます。当サイトはエンドポイントとして生成し、サイト URL の定義を1か所から参照しています。

// src/pages/robots.txt.ts
export const GET: APIRoute = () => {
  const body = `User-agent: *
Allow: /
Disallow: /lab/

Sitemap: ${siteConfig.url}/sitemap.xml
`;
  return new Response(body, {
    headers: { 'Content-Type': 'text/plain; charset=utf-8' },
  });
};

Disallow に入れたパスは、sitemap の filter からも外しておきます。片方だけだと「sitemap で送っているのに robots で拒否している」という矛盾した状態になります。

title と description は frontmatter を正本にする

記事の Markdown の frontmatter を唯一の情報源にして、<title> / <meta name="description"> / OGP / 構造化データすべてに同じ値を流します。ページ側で個別に書くと、必ずどこかがずれます。

当サイトは公開前のチェックでタイトルを20〜40字、description を60〜120字に収まっているか検査しています。ただしこの種の機械チェックは長さしか見ません。中身が薄いままでも通ってしまう点は薄い記事にしないための構成と内部リンクの設計に書いたとおりです。

構造化データは実態に合わせる

JSON-LD で気をつけるのは、型と値を一致させることです。当サイトは記事の著者を Person 型にしたままブランド名を入れていました。人物名ではないので、人物としても組織としても正しく伝わりません。個人名を出さない運用なら Organization にするのが実態に合います。

あわせて、リライトしたときに dateModified を更新してください。更新日を動かさないと、継続的に手入れしていることを示す手がかりが残りません。

OGP画像は記事ごとに事前生成する

OGP 画像を手作業で用意すると必ず抜けます。当サイトはビルド前にスクリプトで記事ごとの PNG を生成し、存在しなければ既定画像にフォールバックする実装にしています。

公開前チェック

  1. トップ・記事・固定ページに固有の title がある
  2. description が短すぎない
  3. ビルド成果物に、Search Console へ送信する予定の sitemap URL が実在する
  4. robots.txt の Sitemap: 行が本番ドメインを指している
  5. 記事 URL を SNS のカード検証ツールで開いて画像が出る
  6. スマホ幅で本文と表が読める

Search Console への登録は、この6点が揃ってから行うとやり直しが減ります。送信後に何を確認すればよいかはSearch Console最初の1週間で確認することを参照してください。

やりすぎなくてよいこと

  • キーワードを不自然に詰め込む
  • 同じ内容の記事を分割して本数を増やす
  • 読者がまだいない段階で細かい順位変動を追う

2番目は実害があります。当サイトは内容の近い記事を並列に増やした結果、AdSense の審査で「有用性の低いコンテンツ」と判定されました。経緯はAdSense審査で「有用性の低いコンテンツ」と判定された原因の記録に書いています。技術タグを整えても、中身の重複は別の問題として残ります。

まとめ

Astro の初期SEOは、title/description・sitemap・robots・OGP・JSON-LD の5点で足ります。ハマりやすいのは sitemap.xml が生成されない点だけで、あとは定義を1か所にまとめておけば破綻しません。設定が済んだら、あとは公開と計測を止めないことのほうが効きます。

関連記事