headless-cms-seo-ssr-pitfalls.html
< BACK 琥珀色と冷たい青色の光に照らされたサーバールームの廊下。ケーブルが暗闇へと消えていく。

ヘッドレスCMS SEO:SSRが害になる場合と修正方法

2021年のある時、クライアントが大慌てで電話をくれた。彼らは4,200個の商品ページを備えたeコマースカタログをヘッドレスのContentfulセットアップで再ローンチし、フロントエンドはNext.jsを使っていた。彼らの代理店は「モダンなスタック、超高速、Googleに愛される」というピッチで売りつけていた。ローンチから6週間後、オーガニックトラフィックは61%減になっていた。クロールエラーではない。手動ペナルティでもない。ただ……消えていたのだ。modern stack, lightning fast, Google will love it. Six weeks post-launch, organic traffic was down 61%. Not crawl errors. Not manual penalties. Just... gone.

重要なポイント:ヘッドレス化はデフォルトではSEO問題を解決しません。クライアント側レンダリング、メタデータ転送の欠落、プレビューURLがインデックスに流出することから、クロールの破損が生じます。Going headless does not sort your SEO by default: broken crawls come from client-side rendering, missing metadata transport, and preview URLs leaking into the index.

同じパターンを何度も見てきた。そして、フラストレーションを感じるのは、SSRは技術的には機能していたということだ。ページはサーバーで描画されていた。HTMLが返されていた。しかし、全体が静かに崩壊していた約7つの他の場所があり、誰もそれをチェックしようとは思わなかった。

このポストはヘッドレスが良いのか悪いのかについてのものではない――それが優れたものになり得るのは明らかだ。SSRがヘッドレススタック上でSEOに対して具体的にどのような方法で失敗するのか、そしてそれについてお前は実際に何をすべきなのかについてのものだ。

---

SSRがヘッドレスSEOを自動的に修正するという神話

ここまでのところだ。2016~2018年頃にクライアント側レンダリングが主流になったとき、SEOコミュニティは集団で大騒ぎした(正当な理由で)。Googleのクローラーは JavaScript実行に対して一貫性がなく、コンテンツはインデックスされないままになり、SPA サイトはランキングを失血していた。だから業界は SSR を特効薬として猛烈に推し進めた。

そして SSR は純粋な CSR より確かに良い。だが「良い」というのは「解決した」という意味ではない。is better than pure CSR. But "better" doesn't mean "sorted."

SSRはレンダリング問題を解決する。キャッシング戦略、クロールバジェット、canonicalの混乱、CMSとHTML <head> の間のメタデータパイプラインについては、ほぼ何もしない。それらは全く別の失敗モードだ。そしてヘッドレスアーキテクチャでは、それらの1つ1つが少なくとも2つのシステム――CMS とフロントエンドフレームワーク――を含み、何をすべきかについて合意する必要がある。<head>. Those are entirely separate failure modes. And in a headless architecture, every single one of them involves at least two systems -- the CMS and the front-end framework -- that need to agree on what to do.

多くの場合、彼らは合意しない。

---

ヘッドレススタックで SSR が実際に SEO を破壊する場所

Time-to-First-Byte の問題

SSR は、あなたのサーバーが高速な場合のみ高速だ。ヘッドレスセットアップでは、Next.js や Nuxt サーバーは応答する前に CMS API からコンテンツを取得しなければならない。Contentful(もしくは Sanity、Storyblok、あるいは他のいずれか)が遅い瞬間を経験している場合、TTFB は急上昇する。設定が悪い SSR セットアップで CMS API コールドスタート中に TTFB が3秒を超えるスパイクを見たことがある。before it can respond. If Contentful (or Sanity, or Storyblok, or whichever) is having a slow moment, your TTFB balloons. I've seen TTFB spike past 3 seconds on poorly configured SSR setups during CMS API cold starts.

Google は TTFB をクロール予定のシグナルとして使う。遅い応答は Googlebot がセッションごとに少ないページ数をクロールすることを意味する。大規模なカタログサイトでは、それは直接、クロール待機キューに数週間留まるページに翻訳される。 Slow responses mean Googlebot crawls fewer pages per session. On a large catalogue site, that directly translates to pages stuck in the crawl queue for weeks.

実行時に生成されるカノニカルタグ

これは多くの人を不意打ちにする。WordPressのような従来型CMSでは、canonicalタグはテーマかSEOプラグインに組み込まれている。ヘッドレスセットアップでは、canonicalロジックはお前のフロントエンドコード内に存在する――Next.js <Head> コンポーネント内かもしれない、レイアウトラッパー内かもしれない。CMSはお前がレンダリングしているcanonicalについて何も知らない。WordPress, canonical tags are baked into the theme or an SEO plugin. In a headless setup, your canonical logic lives in your front-end code -- maybe in a Next.js <Head> component, maybe in a layout wrapper. The CMS has no idea what canonical you're rendering.

では、商品URLにソートやフィルタリング用のクエリパラメータがある場合はどうなるか?あるいはCMSが返すページスラッグがお前のルーティングロジックと若干異なる場合は?結果として、canonicalタグが間違ったURLを指すか、完全に欠けている状態になる。去年、Seahawk のあるプロジェクトで英国の小売業者向けにこれを発見した――800ページがページネーションロジックがSEOコンポーネントに間違ったプロップを渡していたため、/?page=1 にcanonicalizeしていた。見つけるのに2日かかった。修正は3行だった。/?page=1 because the pagination logic was passing the wrong prop to the SEO component. Took two days to find. Three lines to fix.

フォールバックのないメタデータパイプライン

すべてのヘッドレスCMSでは、SEOメタデータフィールド――メタタイトル、説明、OGタグを追加できる。素晴らしい。しかし、エディターがページを公開してそれらを記入し忘れた場合はどうなるか?YoastのあるWordPressなら、生成されたフォールバックが得られるだろう。ヘッドレスセットアップでは、フロントエンドコンポーネントが明示的なフォールバックロジックを持たない場合、空の <title> タグが得られる。あるいはさらに悪いことに、生のフィールド名がHTMLにエコーバックされる。<title> tag. Or worse -- you get the raw field name echoing into the HTML.

フォールバックチェーンを明示的に常に構築せよ:seoTitle ?? pageTitle ?? siteName。すべてのフィールド。例外なし。seoTitle ?? pageTitle ?? siteName. Every field. No exceptions.

---

だれもが十分に考えないキャッシング層

ISR――Next.jsのIncremental Static Regeneration――は本当に賢い。ほぼ静的なパフォーマンスが得られながら、スケジュール上でrevalidateする能力を持つ。しかし、SEO向けには、revalidationウィンドウは実際の結果を伴う決定だ。

revalidate: 3600(1時間)に設定すると、コンテンツの編集は公開後1時間までGooglebotに認識されない。ブログには十分だ。ニュースサイトやフラッシュセールのeコマースページには災難だ。クライアントが4時間の期限付きセールを実施したが、割引キャンペーン計画時にISRウィンドウについて誰も考えていなかったため、45分間キャッシュされた「売切れ」ページが表示されていた。revalidate: 3600 (one hour) and your content edits won't be seen by Googlebot for up to an hour after publish. That's fine for a blog. For a news site or a flash-sale e-commerce page, it's a disaster. I had a client who ran a 4-hour limited sale and spent 45 minutes of it with a cached "sold out" page because nobody had thought about the ISR window when the discount campaign was planned.

修正は常に「より積極的にrevalidateする」ことではない。より頻繁なrevalidationはより多くのオリジン負荷を意味する。実際の修正はオンデマンドrevalidation――コンテンツが公開されたときCMSウェブフックからキャッシュパージをトリガーすることだ。Next.jsはv12.2以降、オンデマンドISRをサポートしている。Contentful、Sanity、Storyblokはすべて送信ウェブフックをサポートしている。それらを接続する。約1日で実装できる。on-demand revalidation -- trigger a cache purge from your CMS webhook when content is published. Next.js has supported on-demand ISR since v12.2. Contentful, Sanity, and Storyblok all support outgoing webhooks. Wire them together. It takes about an afternoon.

---

クロールバジェットとヘッドレスURL表面

従来型CMSプラットフォームはURL周辺に何年もの慣例を持っている――タクソノミー、ページネーション、アーカイブのcanonical処理。ヘッドレスセットアップはお前に完全な自由をくれる。つまり、お前はそれらの決定をすべて自分でコード内で下さなければならないということだ。

注意を払わないときの自由は危険だ。

ヘッドレス商品カタログにファセット検索があると、/products?colour=red&size=M&sort=price-asc のような数万個のユニークURLと、その全ての組み合わせが簡単に生成される。SSR層がそれらすべてをユニークなHTMLでレンダリングし、ベースURLへのcanonicalがなければ、Googlebotに無限迷路を与えたも同然だ。/products?colour=red&size=M&sort=price-asc and every permutation thereof. If your SSR layer is rendering all of those with unique HTML and no canonical pointing back to the base URL, you've just handed Googlebot an infinite maze.

ヘッドレスビルドのたびに私が実装するいくつかのことがある:

  • robots.txtでSEO的に重要でないクエリパラメータURLをすべてブロックrobots.txt that aren't SEO-significant
  • フィルター済みおよびソート済みのすべてのバリアントに対して、クリーンなベースURLを指す単一の正規化を実装
  • 小規模サイトではページ2以降のページネーションページに<meta name="robots" content="noindex, follow">を使用<meta name="robots" content="noindex, follow"> on paginated pages beyond page 2 for smaller sites
  • XMLサイトマップを実際にGooglebotがクロールしているもの(Google Search Consoleのカバレッジレポート)と比較する——初回では両者がまず一致することはない。

サイトマップはビルド時の静的生成ではなく、CMSから動的に生成する。最後のデプロイ時点のコンテンツだけが反映されたサイトマップは、デプロイ間に編集者が40ページ追加した場合は役に立たない。

---

構造化データのギャップ

ヘッドレスCMSは構造化コンテンツに優れている。スキーマ、フィールド型、リファレンス——SanityもContentfulもデータをきれいにモデル化する。だがSEO向けの構造化データ(JSON-LDスキーマ——Product、Article、BreadcrumbListなど)はまったく別の問題だ。

私が監査したヘッドレスフロントエンドのセットアップの多くは、JSON-LD がまったくないか、レイアウトに付け足された単一の汎用 WebSite スキーマしかありません。これは見逃しです。商品ページでは、CMS から取得したライブの価格、在庫状況、レビューデータを含む Product スキーマが必要です。レシピページやハウツーページでは、適切なスキーマは Google のリッチリザルトに直接影響を与える可能性があります。WebSite schema bolted onto the layout. That's a miss. On a product page, you want Product schema with price, availability, and review data pulled live from your CMS. On a recipe or how-to page, the appropriate schema can directly influence rich results in Google.

実装は複雑ではありません。Next.js では、JSON-LD を <Head> 内の <script type="application/ld+json"> タグに入れて、ページプロパティから値を入力し、Google の Rich Results Test でテストするだけです。複雑なのは、CMS のコンテンツモデルがフロントエンドが利用すべき適切なフィールドを提供するようにすることです。これは開発チケットではなく、コンテンツアーキテクチャに関する話し合いです。<script type="application/ld+json"> tag inside <Head>, populate it from your page props, and test it in Google's Rich Results Test. What is complicated is making sure your CMS content model surfaces the right fields for the front-end to consume. That's a content architecture conversation, not a dev ticket.

---

メタデータパイプライン全体の修正

私がヘッドレス SEO 監査のたびに実行する正確なチェックリストをお見せします。概念的なものではなく、実際のステップです。

  1. レンダリング済みHTMLを検証する——curl -A "Googlebot" [your URL] を使い、生のレスポンスを確認する。ブラウザがハイドレーション後に表示するものではなく、<head>に実際に何が入っているか。サーバーからの生のレスポンスを見る。 -- Use curl -A "Googlebot" [your URL] and inspect the raw response. What does the <head> actually contain? Not what your browser shows after hydration. The raw server response.
  2. ランダムに選んだ20ページのcanonicalの正確さを確認する——特にパラメータを持つ商品・カテゴリページ。サイトが大きい場合は、node-fetchを使った小さなスクリプトでcanonicalを大規模にプル・パースする。 -- Especially product/category pages with parameters. Build a small script with node-fetch to pull and parse canonicals at scale if the site is large.
  3. 3つの場所からTTFBをテストする——私はLondon、Frankfurt、Virginiaからのbot UAでWebPageTestを使う。どの場所でも一貫して800msを超えている場合は、その他の問題より前に、CMSのAPI応答時間を調べる。 -- I use WebPageTest with Googlebot UA from London, Frankfurt, and Virginia. If any location is above 800ms consistently, dig into your CMS API response times before anything else.
  4. サイトマップをGSCと比較する——Search Consoleのカバレッジレポートをエクスポートしてエクスポートする。サイトマップ内の「有効」なURLをカバレッジレポートと比較する。サイトマップに含まれていて「除外」されているURLは調査が必要だ。 -- Export the Coverage report from Search Console. Compare "Valid" URLs to your sitemap. Any URL in the sitemap that's "Excluded" needs investigation.
  5. 重複した`<title>`と`<meta description>`タグがないか確認する——レイアウトコンポーネントとページレベルコンポーネントの両方がメタデータを書き込もうとするときにこれは起こる。 -- Happens more than you'd think when layout components and page-level components both try to write metadata.
  6. オンデマンド再検証をエンドツーエンドでテストする——CMSでコンテンツの変更を公開する。サーバーレンダリングページで稼働するまでどのくらいかかるか。時間単位で計測される場合は、ウェブフックを接続する。 -- Publish a content change in your CMS. How long before it's live on the server-rendered page? If it's measured in hours, wire up the webhook.
  7. 代表的なページタイプ(Product、Article、FAQ など最小限)の構造化データを検証する。ローカルではなく、Google の Rich Results Test を本番 URL で実行する。 -- Product, Article, FAQ at minimum. Use Google's Rich Results Test on the live URLs, not just locally.

---

私が実際に使うツール

理論的なリストではない。これは私がヘッドレスSEOの修正に取り組んでいるときにマシン上で開いているツールだ。

  • Screaming Frog -- レンダリングモードで本番サイトをクロールして、Googlebot が見るものを確認する。まず「None」でレンダリングモードを設定して raw SSR 出力を見て、その後「JavaScript」モードと比較する。 -- Crawl the live site in rendering mode to see what Googlebot sees. Set the rendering mode to "None" first to see raw SSR output, then compare to "JavaScript" mode.
  • WebPageTest -- TTFB、サーバレスポンスのウォーターフォール、CDN エッジのヒット/ミスヘッダー。 -- TTFB, server response waterfall, CDN edge hit/miss headers.
  • Google Search Console -- カバレッジレポート、特定ページの URL インスペクション、ページタイプ別の Core Web Vitals。 -- Coverage report, URL Inspection for specific pages, Core Web Vitals by page type.
  • Postman または `curl` -- CMS API を手動でクエリして、SSR レイヤーに実際に返されているデータを確認する。 -- For manually querying CMS APIs to check what data is actually being returned to the SSR layer.
  • Next.js の組み込みログ -- よく見落とされている。ステージングオーディット中に詳細ログを有効にすると、レンダーが待機している場所が正確に浮き彫りになる。 -- Often overlooked. Turning on verbose logging during a staging audit will surface exactly where your render is waiting.

率直に言うと、Screaming Frog だけから見える headless SEO の問題は、私が見つけるもののうち 80% を占める(何を探すべきか知っていれば)。

---

FAQ

Next.js と SSR で SEO が必ずうまくいくのか?

いいえ。SSR はサーバ上で HTML をレンダリングしてからクライアントに到達させることを意味する。これは必要だが十分ではない。正しい canonical タグ、適切なサイトマップ、適切なメタデータ、構造化データ、高速なサーバレスポンスタイムが必要だ。SSR は JavaScript レンダリング問題を解決する。アーキテクチャの問題は解決しない。

SEO の観点では Contentful は Sanity より優れているのか?

CMS はどちらも SEO に直接的には影響しない。ヘッドレスなので、レンダリングされた HTML について意見はない。問題は、どちらが SEO 関連のコンテンツフィールドのモデリングを簡単にするかということだ。どちらも SEO フィールドプラグインを持っている。Sanity の GROQ クエリ言語は、フロントエンドが必要とする正確なデータ形成に際してより多くの柔軟性を提供し、クリーンなメタデータパイプラインの構築を簡単にする可能性がある。しかしこれは SEO の議論ではなく、開発者体験の議論だ。GROQ query language gives you more flexibility in shaping the exact data your front-end needs, which can make it easier to build a clean metadata pipeline. But that's a developer experience argument, not an SEO argument.

ヘッドレスセットアップでhreflangをどう処理しますか?

他のメタデータと同じ方法で処理する。CMS データからサーバ側でそれを生成し、すべてのページの <head> に注入する。複雑さは CMS 内のロケール対 URL マッピングを維持し、フロントエンドがそれを正しく消費していることを確認することにある。Next.js を使っている場合、i18n 設定がルーティング側の多くを処理する。コンテンツデータから <link rel="alternate" hreflang="..."> タグを明示的にレンダリングする必要がある。<head> on every page. The complexity is in maintaining the locale-to-URL mapping in your CMS and making sure the front-end consumes it correctly. If you're on Next.js, the i18n config handles a lot of the routing side; you still need to explicitly render the <link rel="alternate" hreflang="..."> tags from your content data.

SEOを向上させるためにSSRの代わりにSSGを使うべきですか?

コンテンツ更新頻度によって異なる。完全な静的生成(SSG)は最速の TTFB を提供する。すべてのコンテンツがデプロイ時に事前構築されるが、ISR を使用していない限りコンテンツ更新はデプロイ時のみに反映される。ほぼ静的なマーケティングサイトの場合は、オンデマンド ISR を備えた SSG がおそらく適切な選択肢だ。在庫変更が頻繁な大規模カタログの場合は、積極的な CDN キャッシュと短寿命のキャッシュヘッダーを使用した SSR がより適切だ。

---

不都合な真実は、ヘッドレススタックが過去のどのCMSアーキテクチャよりも開発者の肩にSEO責任を重くのしかからせるということです。インストールして処理してくれるプラグインは存在しません。正規URLロジックからサイトマップ生成、構造化データまで、すべての判断がコード判断になります。つまり、それらの判断のすべてが間違っている可能性があり、ほとんどのチームはランキングがすでに悪い方向に動き始めるまでそれらを監査しません。

先手を打ちましょう。Googlebotがするのと同じようにあなた自身のサイトをクロールしてください。問題はほぼ常にGoogleが見つける前に見つけることができます。

< BACK