AngularのSEO
Angularアプリをクロール可能かつインデックス可能にする方法を解説します。@angular/ssrとプリレンダリング、ハイブリッドレンダリング、ハイドレーション、Title/Metaサービス、History APIルーティング、Googlebotが実際にレンダリングするもののテストを扱います。
言語
Angular SEOの本質は、クライアントレンダリングのシェルではなく、本物のHTMLを提供するという1つの判断です。最新のAngular(v17以降)は@angular/ssrをCLIに組み込み、静的ルートをプリレンダリングし、動的ルートをサーバーでレンダリングし、HTMLを破棄しないようハイドレーションします。さらに、AngularのTitle/Metaサービスで固有のtitleとdescriptionを設定し、HTML5 Historyルーティング(ハッシュURLは使わない)、本物の<a href>リンクを使い、URL検査で確認します。動的レンダリングは回避策であり戦略ではありません。AngularJSは別のフレームワークです。
TL;DR — AngularはデフォルトではJavaScriptでブラウザー内にページを構築するため、検索エンジンが最初に見る生HTMLはほぼ空です。解決策は、Angularの組み込みサーバーサイドレンダリング(
@angular/ssr)またはプリレンダリングで、完成した本物のHTMLを送ることです。そのうえで、すべてのページに固有のtitleとdescriptionを設定し、#のないきれいなURLと本物の<a href>リンクを使います。
問題を一文で言うと
標準のAngularアプリは、小さなHTMLシェル(基本的には空の<div>)と大きなJavaScriptバンドルをブラウザーへ送ります。ブラウザーがJavaScriptを実行し、その後ページにコンテンツが入ります。これが**クライアントサイドレンダリング(CSR)**です。
問題は、検索エンジンがそのURLを最初にダウンロードすると、空のシェルしか見ないことです。GoogleはJavaScriptを実行して実際のコンテンツを見ることができますが、それは後で別の段階に行われ、常に確実とは限りません。 Evidence for this claim Google can render JavaScript but processes rendering as a stage after crawling. Scope: Google Search rendering; other crawler behavior is not covered by this record. Confidence: high · Verified: Google: JavaScript SEO basics Bingなどの他のクローラーや、ソーシャルプレビューのボットはJavaScriptをまったく実行できないことが多く、何も見ません。
解決策:完成したHTMLを送る
ブラウザー(またはクローラー)にページを構築させるのではなく、事前に、またはサーバー上で構築して完全なHTMLを送ります。Angularには主に2つの方法があります。
- サーバーサイドレンダリング(SSR) — サーバーがリクエストごとにAngularを実行し、完全なページを返します。頻繁に変わるコンテンツに向きます。 Evidence for this claim Angular supports server-side rendering and build-time prerendering through its SSR tooling. Scope: Current Angular SSR and prerender features. Confidence: high · Verified: Angular: Server-side and hybrid rendering
- プリレンダリング — ビルド時にページの静的HTMLファイルを作るため、サーバーは不要です。最も高速で、ブログ記事やマーケティングページに適しています。
最新のAngular(バージョン17以降)は、この2つをツールキットに組み込んでいます。1つのコマンドで追加できます:ng add @angular/ssr。(以前のAngular Universalという名前を聞いたことがあるかもしれません。別アドオンとして提供されていた同じ考え方です。Angularはv17でこれをコアに統合し、名前を変更しました。)
もう一つの基本
- すべてのページに固有のtitleとdescriptionを付ける。 Angularが自動で行うことはないため、組み込みの
TitleとMetaサービスを使ってコードで設定します。これがないと、すべてのページで同じtitleになります。 - ハッシュURLではなく、きれいなURLを使う。 Angularの標準ルーティングなら
/products/shoesのようなURLになります。古い「ハッシュ」形式(/#/products)は避けます。検索エンジンは#の後を確実に処理できません。 - 本物のリンクを使う。 ナビゲーションはボタンやクリックハンドラーではなく、本物の
<a href>リンクでなければGoogleが追跡できません。
多くの人が最も誤解していること
「GoogleはAngularをインデックスできない」は神話です。 GoogleはJavaScriptをレンダリングできます。しかし完成したHTMLを送るより遅く、確実性も低く、Google以外のクローラーはまったく処理できません。見つけてほしいものは、サーバーでレンダリングするかプリレンダリングしてください。
より詳しい版(ルートごとのハイブリッドレンダリング、ハイドレーション、コード付きのSEOサービス、Googleが実際に見るもののテスト方法)を読みたい場合は、Advancedタブに切り替えてください。
Evidence for this claim Modern Angular documents @angular/ssr as its server-side and hybrid rendering package. Scope: Current Angular documentation; historical Angular Universal details are not required for this claim. Confidence: high · Verified: Angular: Server-side and hybrid renderingTL;DR — Angular SEOは、多くの役割を持つ1つのアーキテクチャ上の判断です。クライアントレンダリングのシェルではなく、本物のHTMLをレスポンスに入れます。最新のAngular(v17以降)は、SSRを
@angular/ssrとしてCLIに組み込みました(改称され、統合されたAngular Universalの後継です)。静的ルートをプリレンダリングし、動的ルートをサーバーでレンダリングし、ハイブリッドレンダリングで組み合わせ、サーバーHTMLを再利用して再構築しないようハイドレーションします。さらに、Title/Metaサービス(またはルーターのTitleStrategy)で固有のtitle/descriptionを設定し、HTML5 Historyルーティング(HashLocationStrategyは使わない)、本物の<a href>リンク、安全に注入したJSON-LD、URL検査による検証を行います。動的レンダリングはGoogleが認める回避策であり、戦略ではありません。
問題はデフォルト:クライアントサイドレンダリング
標準のAngularビルドは、本文が実質的に<app-root></app-root>で、スクリプトタグが付いたindex.htmlを出荷します。JavaScriptを実行しなければ、クローラーには見出し、本文、リンクがありません。バンドルの読み込み後にブラウザーでコンテンツが組み立てられるからです。これはJavaScript SEO で説明しているのと同じ中核問題です。ウェブは単純なHTMLから移行し、CSRはそのスペクトルの中でもリスクが高い端です。
(このガイドの実装詳細(RenderMode API、ハイドレーションのトリガー、イベントリプレイ)はAngular v22を反映しています。以下のバージョン固有の機能には、v17のSSR改称、v18のイベントリプレイ、v19〜v20のインクリメンタルハイドレーションという日付があります。)
CSRは、SEOに関する3つの異なる問題を生みます。
- インデックス登録の遅延と不確実性。 GoogleはJSアプリを3段階(「Crawling, Rendering, and Indexing」 (翻訳)「クロール、レンダリング、インデックス登録」)で処理し、レンダリングをキューへ遅延させます。レンダリングが実行されるまで、コンテンツはインデックス登録の対象になりません。
- Core Web Vitalsの悪化。 意味のあるコンテンツを描画する前にブラウザーがバンドルをダウンロードして実行するため、LCPが悪化します。
- Google以外のクローラーにはシェルしか見えない。 BingbotはJSレンダリングの一貫性が低く、ソーシャル/リンクプレビューのボットは通常レンダリングしません。そのため、クライアント側で注入したOpen Graphタグやコンテンツは届きません。
GooglebotがAngularアプリを実際に処理する方法
Googlebotはevergreenです。現在のChromeのV8エンジンでレンダリングし、Chromeのリリースとともに更新されます。 Evidence for this claim Googlebot uses an evergreen version of Chromium for rendering. Scope: Google Search rendering engine; this does not remove application-level rendering risks. Confidence: high · Verified: Google: Evergreen Googlebot したがってAngularを実行できます。ただし、設計時に考慮すべき明確な境界があります。
- レンダリングは即時ではなくキューに入る。 Googleはクロール、レンダリング、インデックス登録を別々の段階として文書化していますが、レンダリングがクロールに追いつくまでの固定時間は公開していません。業界でいう「2つの波」は、まずCSR Angularの空のシェルである生HTMLがインデックスされ、その後、レンダリングされたDOMが、レンダリングが実行された時点でインデックスされることです。SSR/プリレンダリングなら最初の取得でHTMLが完成しているため、そのコンテンツが別のレンダリングを待つ必要はありません。
- レンダラーはステートレス。 読み込み間でCookie、
localStorage、sessionStorageを持ち越さず、権限プロンプトを拒否します。クライアント状態でコンテンツを制御しないでください。 - クリックもスクロールもしない。 とても高いビューポートでレンダリングします。操作の背後にあるコンテンツは見られません。
- リソースは積極的にキャッシュされる。 更新されたバンドルが古いまま配信されないよう、コンテンツフィンガープリント付きファイル名(Angularの標準
main.<hash>.js)を使います。
@angular/ssrとは:名称の歴史
サーバーサイドレンダリングは、リクエストごとにNodeサーバー上でAngularを実行し、完全にレンダリングしたHTMLを返します。ブラウザーは既存のDOMにイベントリスナーを取り付けてハイドレーションします。すべてのクローラー(Googlebot、Bingbot、ソーシャルボット)が最初のリクエストで完全なHTMLを受け取り、2つ目の波を待つ必要がありません。
名称が混乱を招くため、正確に説明します。**「Angular Universal」**は歴史的なSSRソリューションで、外部パッケージ(@nguniversal/express-engine)として提供されていました。**Angular v17(2023年11月)でSSRはAngular CLIとApplication Builderに直接統合され、@angular/ssr**へ改称されました。Angular Universalリポジトリは現在メンテナンスモードです。 Evidence for this claim Modern Angular documents @angular/ssr as its server-side and hybrid rendering package. Scope: Current Angular documentation; historical Angular Universal details are not required for this claim. Confidence: high · Verified: Angular: Server-side and hybrid rendering 現在の設定は1行です。
# New project with SSR enabled
ng new my-app --ssr
# Add SSR to an existing project
ng add @angular/ssrこれは古いng add @nguniversal/express-engineを置き換えます。同じ考え方ですが、正式に統合されたツールです。
プリレンダリング(静的生成/SSG)
プリレンダリングはビルド時にルートの静的HTMLを生成するため、リクエスト時にサーバーを実行せず、CDNへデプロイできます。TTFB/FCP/LCPが最も速く、運用コストも最小です。v17以降は、サーバールートファイル(app.routes.server.ts)でRenderMode.Prerenderを使い、ルートごとに設定します。outputMode: 'static'なら完全に静的なアプリになります。
制約は、データがビルド時に利用できなければならず、ユーザーごとのコンテンツがなく、大規模サイトではビルドが遅くなることです。マーケティングページ、ドキュメント、ブログ記事に最適です。検索でランク付けされ、引用される必要が最も高いコンテンツそのものです。
ハイブリッドレンダリング — ルートごとにモードを選ぶ
v17以降の大きな進歩は、app.routes.server.tsでレンダリングモードをルートごとに設定できることです。
RenderMode.Prerender— 静的ルート(ホーム、About、ブログ記事)。RenderMode.Server— 動的でリクエストごとに変わるルート(検索結果、最新データのダッシュボード)。RenderMode.Client— そもそもインデックスさせない内部専用ルート(管理画面、認証限定画面)。ここではCSRで問題ありません。
実務での判断ルールはこれです。静的なものはプリレンダリングし、最新性が必要なものはサーバーでレンダリングし、インデックスに入れるべきでないものだけクライアントレンダリングへ戻します。
ハイドレーション — CLSを壊す落とし穴
素朴なSSRには欠点があります。サーバーがHTMLを送り、ブラウザーがそれを破棄して最初から再レンダリングするため、ちらつきと無駄な処理が発生します。ハイドレーションはこれを解決します。ブラウザーがサーバーでレンダリングしたアプリを復元し、対応するDOMを破棄・再作成せず再利用して、インタラクティブ性だけを接続します。これはapp.config.tsで有効にします。
provideClientHydration()前提: ハイドレーションはSSRに付随するクライアント側の機能で、単独のスイッチではありません。provideClientHydration()が動くのは、すでにサーバーでレンダリング(またはプリレンダリング)されたルートだけです。RenderMode.ClientルートのシェルをサーバーHTMLに変えることはできません。まずそのルートでSSR/プリレンダリングを有効にします。
SEOに隣接するUXで重要な新機能が2つあります。ただし、どちらも検索のインデックス登録自体を変えません。
- イベントリプレイ(v18以降): ハイドレーション完了前に起きた、対応対象のユーザー操作を取得し、完了後に再生します。対応する操作で失われるクリックを減らします。UX/インタラクティブ性の機能でありSEOの機能ではなく、Googleがインデックスする内容にも影響しません。
- インクリメンタルハイドレーション(v19開発者プレビュー、v20で安定版): SSR、ハイドレーション、遅延可能ビュー、イベントリプレイを組み合わせて使い、単独機能ではありません。
@deferブロックのhydrateトリガーで、初期レンダリングでどの境界を脱水状態に保つかを制御します。hydrate never境界は初回読み込みで脱水状態のままですが、後のクライアントレンダリング(ルート変更後など)で依存関係の読み込みまで必ず阻止するわけではありません。SEOへの影響は間接的です。最初にハイドレーションするJavaScriptを減らすとLCPに役立つことがありますが、トリガー設定はレンダリングの詳細であって、クローラーのタイミングを制御する仕組みではありません。
重大な落とし穴: テンプレート内に@if (isPlatformBrowser(...))を直接置くと、サーバーとクライアントが異なるマークアップをレンダリングし、ハイドレーション不一致になります。レイアウトシフトが発生してCLSを悪化させるため、テンプレートをプラットフォームで分岐せず、ブラウザー専用処理にはafterNextRender()を使います。
titleとmetaタグ — Angular組み込みサービス
Angularは<title>要素のテキストへ直接バインドできないため、@angular/platform-browserの2つのサービスでheadタグを管理します。
Title—setTitle()/getTitle()。Meta—addTag()、addTags()、updateTag()、getTag()、removeTag()。たとえばname='description'やproperty='og:title'のようなセレクターを使います。
基本機能にサードパーティーライブラリは不要です。共有SEOサービスがきれいなパターンです。
@Injectable({ providedIn: 'root' })
export class SeoService {
private title = inject(Title);
private meta = inject(Meta);
updatePage(title: string, description: string) {
this.title.setTitle(title);
this.meta.updateTag({ name: 'description', content: description });
this.meta.updateTag({ property: 'og:title', content: title });
}
}特にtitleについては、ルーターの**TitleStrategy**(Angular v14以降)を使うと、ルート設定でtitleを直接設定でき、ナビゲーション時にページtitleが自動更新されます。コンポーネントごとのコードは不要です。document.title = ...を手で設定せず、SSRでも正しく動くようTitleサービスを使います。
URL構造
- 標準のHTML5 History APIルーティング(
PathLocationStrategy)を使う。/products/shoesのようなきれいなURLになります。index.htmlに<base href="/">が必要です。 - 公開コンテンツで
HashLocationStrategy/useHash: trueを決して使わない。#の後のフラグメント識別子はHTTPリクエスト前に取り除かれるため、サーバーには見えず、Googlebotも#/productsを確実に解決できません。サイト全体が1つのURLに崩れる可能性があります。 - ナビゲーションには本物の
<a href>リンクを使う。 遅延読み込みルートへのリンクも同じです。遅延読み込みはパフォーマンス上問題ありませんが、ルートへのリンクはクリックハンドラーではなくクロール可能なアンカーでなければなりません。
構造化データ(JSON-LD)
GoogleはJavaScriptによるJSON-LDの注入をサポートしています。堅牢なAngularパターンは、documentをグローバルに触ってサーバーを壊すのではなく、AngularのDOCUMENT注入トークンで<script type="application/ld+json">を作り、document.headへ追加するサービスです。すべてのスキーマを1か所に置き、静的HTMLとレンダリングされたDOMに分割しないでください。リッチリザルトテストとURL検査で検証します。
動的レンダリング — 旧式の回避策であり、計画ではない
動的レンダリングは、ボットにPuppeteer、Rendertron、prerender.ioなどでプリレンダリングした版を、ユーザーに完全なSPAを提供します。Googleは*“dynamic rendering is a workaround and not a long-term solution,”* (翻訳)「動的レンダリングは回避策であり、長期的な解決策ではない」、そして*“better solutions than dynamic rendering”* (翻訳)「動的レンダリングより優れた解決策」があると明確に述べています。つまり、サーバーサイドレンダリング、静的レンダリング、ハイドレーションです。実質的に同じコンテンツを提供する限り自動的にクローキングになるわけではありませんが、追加のレンダリングサーバー、コンテンツのずれのリスクを生み、実ユーザーのCore Web Vitalsには何もしません。新しいAngularビルドでは、代わりに@angular/ssrまたはプリレンダリングを使います。
Angular SEOでよくある間違い
- ハッシュルーティング(
#URL) — サイト全体が1つのURLに見える。 Title/Metaの呼び出しがない — すべてのページが同じtitleとdescriptionを共有する。- SSR/プリレンダリングがない — コンテンツが遅延レンダリングの波の後でしか存在しない。
robots.txtで.js/.cssをブロックする — Googleがレンダリングできず、空のシェルをインデックスする。- 見つからない画面で
200を返す — ソフト404。本物の404を返すかnoindexを追加する。 Titleサービスではなくdocument.title = ...を使う。- テンプレートの
@if内でisPlatformBrowser()を使う — ハイドレーション不一致 → CLS。 - サーバーで実行されるコードから
window/localStorage/documentに触る — SSRがクラッシュする。 - 生HTMLとレンダリングされたDOMでスキーマを分割する。
- URL検査ではなくローカル開発環境でテストする — Googlebotではなく思い込みに頼る。
AngularをSEO向けにテストする
- URL検査(Search Console)が真実の基準です。クロール済みページ表示には、GooglebotがJavaScriptを実行した後のDOMであるレンダリングHTML、インデックスされる内容、JSコンソールメッセージ、ブロックされたリソースが表示されます。オンデマンドレンダリングにはライブテストを実行します。
- URLを
curlすると、生のJavaScript実行前HTMLが分かります。空のシェルならSSRのないCSRです。 - リッチリザルトテストで構造化データを検証します。
- Ahrefs Site Audit(JSレンダリングを有効化)とScreaming Frog(JSモード)で、生DOMとレンダリングDOMを大規模に比較します。
- Lighthouse/PageSpeed Insightsで、レンダリング方式がCore Web Vitalsに与える影響を確認します。
AngularはSEOに悪いのではなく、違うだけです。レスポンスに本物のHTMLを入れ、headタグを管理し、URLとリンクをクロール可能にし、何がレンダリングされたかをGoogle自身のツールで判断させます。このトピックはJavaScript SEO と、同じクラスターのヘッドレスCMSレンダリングの問題に並びます。根底の教訓は共通です。レンダリングモードがほぼすべてを決めます。
AIによる要約
Advanced版の要点をまとめると次のとおりです。
- Angularのデフォルトはクライアントサイドレンダリング(CSR)。 ほぼ空のHTMLシェルなので、インデックス登録が遅く不確実になり、LCPも悪化し、Google以外のクローラー(Bing、ソーシャル)は何も見ません。
- GooglebotはevergreenでJSをレンダリングする。 ただしレンダリングはキューに入る(「2つの波」—まず生HTML、後でレンダリングDOM。固定時間なし)、ステートレス(Cookie/ストレージなし)、クリックもスクロールもせず、リソースを強くキャッシュします。SSR/プリレンダリングなら最初の取得でHTMLが完成するため、待ち時間を回避できます。
- 現代的な解決策は
@angular/ssr。 v17以降、SSRがAngular CLIに組み込まれ、外部で保守されるAngular Universal(@nguniversal/express-engine)に取って代わりました。設定はng new --ssrまたはng add @angular/ssrです。 - **プリレンダリング(ビルド時の静的HTML)**は最速でCDNへデプロイできます。静的なマーケティング、ブログ、ドキュメントに最適ですが、ビルド時に利用できるデータに限られます。
- ハイブリッドレンダリングは
app.routes.server.tsでルートごとにモードを設定します。RenderMode.Prerender(静的)、RenderMode.Server(動的)、RenderMode.Client(インデックスしない内部ページ)。 - ハイドレーション(
provideClientHydration())は、すでにSSR/プリレンダリングされたルートでサーバーHTMLを再利用します。CSR用のサーバーHTMLは作りません。イベントリプレイ(v18以降)はハイドレーション前の対応操作を取得して後で再生し、インクリメンタルハイドレーション(v19プレビュー/v20安定版)はSSR+ハイドレーション+遅延可能ビュー+イベントリプレイを組み合わせ、@deferのhydrateトリガーで初期読み込み時の境界を制御します。hydrate neverは後のクライアント読み込みを阻止しません。どちらも検索のインデックス登録を変えません。@if内のisPlatformBrowser()は避けてください。ハイドレーション不一致からCLSが起きるため、afterNextRender()を使います。 - 組み込みの
Title/Metaサービスを使う。 サードパーティーライブラリは不要です。ルーターの**TitleStrategy**(v14以降)でルートごとのtitleを自動設定できます。 - HTML5 Historyルーティングを使い、ハッシュ(
#)URLは使わない。 ナビゲーションには本物の<a href>アンカーを使います。JSON-LDはDOCUMENTトークンで注入できます。 - 動的レンダリングは戦略ではなく回避策。 GoogleはSSR/静的レンダリング/ハイドレーションを勧めています。
- URL検査(レンダリングHTML)、
curl(生HTML)、リッチリザルトテスト、JSレンダリングクローラーでテストします。AngularJSはAngularと異なります。 古いAngularJSの助言は適用しないでください。
公式ドキュメント
GoogleとAngularによる一次情報のドキュメントです。
- JavaScript SEOの基本を理解する — クロール→レンダリング→インデックス登録の段階、クロール可能な
<a href>リンク、フラグメントルーティングへの注意、ソフトなエラー、JSで注入する構造化データ。 - 動的レンダリング(回避策) — 回避策である理由、クローキングの注意点、SSR/静的レンダリング/ハイドレーションという代替策。
- ウェブでのレンダリング(web.dev — Addy Osmani、Jason Miller) — SSR、CSR、静的レンダリング、ハイドレーションの基準となる定義とパフォーマンス上のトレードオフ。
- URL検査ツール — Googleが実際にインデックスするレンダリングHTMLと、JSコンソールメッセージを確認する方法。
Angular
- Angular — サーバーサイド/ハイブリッドレンダリング(SSR) — 公式の
@angular/ssrガイド、サーバールート、RenderMode。 - Angular —
Titleサービス —setTitle()/getTitle()。 - Angular —
Metaサービス —addTag()、updateTag()、セレクター。 - Angular — Routerリファレンス —
PathLocationStrategyとハッシュルーティングの違い、TitleStrategy。 - Angular v17の紹介(Angularチームブログ) — SSRがCLIの正式な機能になったこと。
- Angular Universal(メンテナンスモード) — 現在は
@angular/ssrに置き換えられた歴史的パッケージ。
原文からの引用
Googleと私自身の執筆による、記録に残る発言です。各検索エンジンリンクは、原文ページの引用箇所へ直接移動します。
Google — JavaScriptアプリの処理方法
- “Google processes JavaScript web apps in three main phases: 1. Crawling 2. Rendering 3. Indexing.” (翻訳)「GoogleはJavaScriptウェブアプリを、クロール、レンダリング、インデックス登録という3つの主な段階で処理します。」 — Google Search Centralのドキュメント。 引用箇所へ
- “Google can only discover your links if they are <a> HTML elements with an href attribute.” (翻訳)「Googleがリンクを発見できるのは、href属性を持つ<a> HTML要素の場合だけです。」 — Google Search Centralのドキュメント。 引用箇所へ
Google — 動的レンダリングは回避策
- “Dynamic rendering was a workaround and not a long-term solution for problems with JavaScript-generated content in search engines.” (翻訳)「動的レンダリングは回避策であり、検索エンジンにおけるJavaScript生成コンテンツの問題への長期的な解決策ではありません。」 — Google Search Centralのドキュメント。 引用箇所へ
web.dev — SSR/静的レンダリングを優先(Addy Osmani、Jason Miller)
- “Rendering an app on the server to send HTML, rather than JavaScript, to the client.” (翻訳)「JavaScriptではなくHTMLをクライアントへ送るため、サーバー上でアプリをレンダリングすること。」これはサーバーサイドレンダリングの定義です。 引用箇所へ
Patrick Stox (私自身の執筆 — JavaScript SEO: A Definitive Guide)
- “JavaScript is not bad for SEO, and it’s not evil. It’s just different from what many SEOs are used to.” (翻訳)「JavaScriptはSEOに悪いものでも邪悪なものでもありません。多くのSEO担当者が慣れているものと違うだけです。」
- “Any kind of SSR, static rendering, and prerendering setup is going to be fine for search engines.” (翻訳)「どのようなSSR、静的レンダリング、プリレンダリングの設定でも、検索エンジンには問題ありません。」
Angular SEOチェックリスト
Angularアプリがクロール可能でインデックス可能かを確認する簡単なチェックです。
- 公開コンテンツが、完全なCSRではなく
@angular/ssr(SSR)またはプリレンダリングによる本物のHTMLとして配信されている。 - レンダリングモードが
app.routes.server.tsでルートごとに設定されている。静的ルートはプリレンダリング、動的ルートはサーバーレンダリング、インデックスしない内部ページだけクライアントレンダリングにする。 - ハイドレーション(
provideClientHydration())が有効で、テンプレートの@if内でisPlatformBrowser()を使っていない(代わりにafterNextRender()を使う)。 - すべてのページに固有のtitle(
TitleサービスまたはルーターのTitleStrategy)と固有のdescription(Metaサービス)がある。 - ソーシャルプレビュー用のOpen Graph/Twitter Cardタグを
Metaサービスで設定している。 - ルーティングが
<base href="/">付きのHTML5 History API(標準)で、HashLocationStrategy/useHash: trueではない。 - 遅延読み込みルートへのリンクも含め、すべてのナビゲーションが本物の
<a href>アンカーを使っている。 -
robots.txtが.jsや.cssのリソースをブロックしていない。 - クライアント側の見つからない画面が本物の
404を返すかnoindexを持つ(ソフトなエラーではない)。 - サーバーで実行されるコードから
window/localStorage/documentにアクセスしていない(DOCUMENTトークン/プラットフォームガードを使う)。 - JSON-LDを1か所に置き、リッチリザルトテストを通過している。
- ローカル開発だけでなく、URL検査でレンダリングHTMLを確認している。
メンタルモデル
1. レスポンスに本物のHTMLを入れる。 ほぼすべてのAngular SEO問題は、クローラーが最初の取得で完成したHTMLを得るのか、それともレンダリングが必要なシェルを得るのか、という1つの問いに還元できます。SSRとプリレンダリングなら「はい」、CSRなら「いつか、たぶん」です。すべての監査はここから始めます。
2. ルートごとのレンダリング判断ルール。
- 静的コンテンツ(ホーム、About、ブログ、ドキュメント)→
RenderMode.Prerender。 - 動的で最新性が必要なコンテンツ(検索、ライブデータ)→
RenderMode.Server。 - インデックスさせたくない内部/認証ページ→ **
RenderMode.Client**で問題ありません。
3. 「Angular Universal」と「@angular/ssr」は、時代が違う同じ考え方。
Universalは外部パッケージでした。v17でSSRがCLIに取り込まれ、改称されました。最新のAngularなら@angular/ssrを使います。Universalリポジトリはメンテナンスモードです。
4. 再レンダリングせず、ハイドレーションする。
素朴なSSRはHTMLを送り、その後破棄します。provideClientHydration()はそれを再利用しますが、すでにSSR/プリレンダリングされたルートだけで動き、CSRルートのサーバーHTMLは作りません。イベントリプレイとインクリメンタルハイドレーション(@defer)は初期JavaScriptを減らします。テンプレートをisPlatformBrowser()で分岐しないでください。ハイドレーション不一致とCLSにつながります。
5. headタグはAngularではなく自分の仕事。
ここにYoastはありません。ページごとにTitle/Metaサービス(またはTitleStrategy)でtitleとdescriptionを意図的に設定します。「すべてのページが同じtitle」は運の悪さではなく、標準状態での失敗です。
6. きれいなURLのステートレスなボット向けに設計する。
History APIルーティング、本物の<a href>リンク、Cookie/ストレージに依存しないこと、クリックの背後にコンテンツを置かないこと。そのうえで、自分のノートパソコンではなくURL検査に何がレンダリングされたかを判断させます。
Angular SEO — チートシート
レンダリングモード
| モード | HTMLを構築する場所 | SEO | 最適な用途 | Angular設定 |
|---|---|---|---|---|
| Prerender(SSG) | ビルド時 → 静的ファイル | ✅ 最適 | 静的なマーケティング/ブログ/ドキュメント | RenderMode.Prerender |
| SSR | リクエストごとのサーバー | ✅ 良好 | 動的で最新性が必要なコンテンツ | RenderMode.Server/@angular/ssr |
| Client(CSR) | ブラウザー内 | ⚠️ リスクあり | 内部/認証ページ(インデックスしない) | RenderMode.Client |
| 動的レンダリング | 別のボットサーバー | 回避策のみ | 移行できない旧アプリ | Puppeteer/Rendertron/prerender.io |
設定コマンド
| 目的 | コマンド |
|---|---|
| SSR付きの新規プロジェクト | ng new my-app --ssr |
| 既存アプリにSSRを追加 | ng add @angular/ssr |
| ハイドレーションを有効化 | app.config.tsのprovideClientHydration() |
headとルーティングの速習ルール
- title/description:Angularの**
Title+Meta**サービス(サードパーティーライブラリは不要)。 - ルートごとのtitleを自動化:ルートの
titleプロパティ経由でルーターの**TitleStrategy**(v14以降)。 - ルーティング:HTML5 History API+
<base href="/">。決してuseHash: trueにしない。 - リンク:遅延読み込みルートも含め、本物の**
<a href>**アンカー。 - JSON-LD:**
DOCUMENT**トークンで注入し、1か所に保つ。
名称
- Angular Universal=古い外部パッケージ(
@nguniversal/express-engine)、メンテナンスモード。 @angular/ssr=同じSSRで、v17以降CLIに組み込み。- AngularJS(v1.x)≠Angular(v2以降) — 異なるフレームワークであり、古いAngularJSの助言は適用されない。
注意点
- テンプレートの
@if内のisPlatformBrowser()→ ハイドレーション不一致 → CLS。afterNextRender()を使う。 - サーバー上の
window/localStorage/document→ SSRクラッシュ。 - robots.txtで
.js/.cssをブロック → Googleがレンダリングできない。
Angularアプリが実際にサーバーレンダリングされているか確認する
URLがCSRなのかSSR/プリレンダリングなのかを最も速く知る方法は、JavaScriptが実行される前の生HTMLを取得し、本物のコンテンツを探すことです。CSRのAngularアプリはほぼ空の<app-root>を返し、SSR/プリレンダリングされたアプリは完成したマークアップを返します。
macOS / Linux
# Raw HTML as the server sends it — this is the "first fetch", pre-JS
curl -sL -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
https://example.com/page/ -o raw.html
# Is your real headline in the raw HTML? Empty result = CSR with no SSR
grep -o "Your headline text" raw.html
# A near-empty <app-root> is the tell-tale CSR signature
grep -o "<app-root></app-root>" raw.htmlWindows (PowerShell)
$ua = "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
Invoke-WebRequest -Uri "https://example.com/page/" -UserAgent $ua -OutFile raw.html
Select-String -Path raw.html -Pattern "Your headline text"
Select-String -Path raw.html -Pattern "<app-root></app-root>"見出しがなく、裸の<app-root>が見えるなら、コンテンツはレンダリングに依存しています。SSRまたはプリレンダリングを追加してください。(レンダリングされたDOMには、URL検査の「クロール済みページを表示→レンダリングHTML」を使います。通常のcurlはJSを実行できません。)
AngularのJS/CSSをrobots.txtでブロックしていないか確認する
macOS / Linux
curl -sL https://example.com/robots.txt | grep -iE "disallow.*\.(js|css)|Disallow:\s*/(assets|dist|main)"バンドルに一致するDisallowは、Googleがページを正しくレンダリングできないことを意味し、ほぼ常に間違いです。
Angular SEOのデバッグツール
- URL検査(Google Search Console) — 真実の基準です。ライブテストを実行し、レンダリングHTML(GooglebotがAngular JSを実行した後のDOM)、スクリーンショット、ページリソース(読み込まれたものとブロックされたもの)、JavaScriptコンソールメッセージを確認します。
- リッチリザルトテスト — レンダリング方式を変更した後も、レンダリング結果にJSON-LDが存在することを確認します。
curl— JavaScript実行前の生HTMLを取得し、空の<app-root>ならCSR、完成していればSSR/プリレンダリングと判別します。- Ahrefs Site Audit(JSレンダリングを有効化) — ヘッドレスChromeでクロールし、生DOMとレンダリングDOMを比較して、欠落メタデータ、壊れたcanonical、インデックス可能性の問題を大規模に見つけます。
- Screaming Frog SEO Spider(JSレンダリングモード) — URLごとに生コンテンツとレンダリングコンテンツを比較します。
- Lighthouse/PageSpeed Insights — レンダリング戦略(CSR、SSR、プリレンダリング)がCore Web Vitalsに与える影響を測定します。
- Angular CLI/DevTools — ビルドの
outputMode、サーバールート、ハイドレーションが想定どおり設定されていることを確認します。
時間を使う価値のあるリソース
関連する私の執筆
- JavaScript SEO: A Definitive Guide — レンダリング、DOMの同等性、最も制限の厳しいディレクティブのルール、安全なレンダリング設定を扱う私の完全ガイドです。Angular SEOは、この内容を具体化したものです。
- Technical SEO初心者ガイド — レンダリングとクロールが全体像のどこに位置するかを説明します。
私の講演
- JavaScript SEO — Ungagged 2019 (SlideShare)— Googlebotのステートレスなレンダリング動作、ビューポート、キャッシュ、当時のレンダリング方式を扱います。(注意書き:この資料の動的レンダリング推奨は現在古く、Googleは後に回避策と呼んでいます。)
業界の資料
- Rendering on the Web (web.dev)— Addy OsmaniとJason Millerによる、SSR、CSR、静的レンダリング、ハイドレーションのトレードオフを扱う基準となる記事。
- サーバーサイド/ハイブリッドレンダリング(SSR) (angular.dev)— サーバールート、
RenderMode、プリレンダリング、ハイドレーションを扱う公式@angular/ssrガイド。 - Angular v17の紹介 (Angularチームブログ)— SSRをCLIの正式な機能にし、
@angular/ssrパッケージを導入したリリース。 - Angular Universal(メンテナンスモード) (GitHub)—
@angular/ssrに置き換えられた歴史的なSSRパッケージで、改称を理解するのに役立ちます。 - Angular SEO Guide (Search Engine Journal、Jamie Indigo)— 古典的な2つの波によるインデックス登録の解説。v17の改称前ですが、基本に強い資料です。
- Angular SSRガイド (Angular Architects、Alexander Thalhammer、2025年3月)— コード中心のSSR設定ガイド。公開後にAPIが変わっていないか、公式
@angular/ssrドキュメントでコード例を確認してください。 - r/TechSEO — レンダリングとインデックス登録のデバッグを扱うコミュニティー。
Angular SEOのアンチパターン
Angularアプリで繰り返し見かける具体的な間違いです。理論上のリスクとしてではなく、どれも直接確認する価値のある習慣です。
CSRだけのビルドを出荷して完了とする
標準のng newの出力にはSSRもプリレンダリングもありません。プロジェクトを始める最速の方法ですが、最初の取得で空の<app-root>になる最も簡単な方法でもあります。なぜ誤りか: クローラーが見る生HTMLにコンテンツがなく、インデックス登録がGoogleの遅延レンダリングに完全に依存します。他のボットには2回目の機会がありません。代わりに: 見つけてほしいものを出荷する前に、プロジェクトの最初で@angular/ssrを追加(ng new my-app --ssr)するか、既存アプリでng add @angular/ssrを実行します。
公開ルートでHashLocationStrategyを使う
ハッシュルーティング(useHash: true、/#/products/shoesのようなURL)は、古いAngularチュートリアルやボイラープレートでは今も標準です。なぜ誤りか: #の後はリクエストがサーバーに届く前にクライアント側で取り除かれます。そのためサーバーもGooglebotもアプリ全体を1つのURLとしてしか見ません。代わりに: index.htmlで<base href="/">を使う標準のHTML5 History APIルーティング(PathLocationStrategy)にします。
isPlatformBrowser()でテンプレートを分岐する
@if (isPlatformBrowser(platformId))でコンテンツを囲むと、ブラウザー専用コードを保護する明白な方法に感じます。なぜ誤りか: ハイドレーション時にサーバーは一方の分岐を、クライアントは別の分岐をレンダリングし、ハイドレーション不一致になります。Angularが差分を調整するため、画面にはCLSとして現れるレイアウトシフトが起きます。代わりに: ブラウザー専用処理にはafterNextRender()を使い、テンプレート自体はサーバーとクライアントで同じにします。
Titleサービスではなくdocument.titleを直接設定する
ローカル開発では動くため、簡単な近道として使いたくなります。なぜ誤りか: document.title = '...'のような直接DOMアクセスはSSRとうまく動きません。サーバーにはブラウザーと同じ意味でのグローバルdocumentがなく、Angular独自のtitle処理が持つルーター統合の利点も失います。代わりに: AngularのTitleサービス(setTitle())を注入するか、ルーターのTitleStrategyでルートごとにtitleを設定します。
SSR中に実行されるコードでwindow、localStorage、documentに触る
構築時にlocalStorageを読み取ったりwindow.innerWidthを確認したりするコンポーネントやサービスは、ブラウザーでは正常でもサーバーレンダリングをクラッシュさせます。なぜ誤りか: SSRビルドを実行するNodeサーバープロセスにはこれらのグローバルがないため、レンダリングが例外を出し、リクエストがサーバーエラーになるか、空のレスポンスへ静かにフォールバックします。代わりに: afterNextRender()の背後でコードを実行するか、グローバルではなくAngularのDOCUMENTトークンを注入し、ng serveだけでなくSSRビルドをローカルでテストします(ng build+SSR出力の配信)。
動的レンダリングを恒久的な修正として扱う
PuppeteerやRendertronのようなサービスでボットにプリレンダリングしたスナップショットを配信すると、目の前の症状は解決します。なぜ誤りか: 維持するシステムが増え、実ユーザーに見えるものとずれる可能性があり、Googleも回避策であって長期的な解決策ではないと明言しています。代わりに: @angular/ssrまたはプリレンダリングへ移行し、ボットにも人にも同じパイプラインから同じ本物のHTMLを返します。
このルートはどのレンダリングモードを使うべきか?
Angular v17以降では、app.routes.server.tsでルートごとにレンダリングモードを設定できます。アプリ全体を「SSRかプリレンダーか」で決めるのではなく、ルートごとに問う問題です。
Choosing a rendering mode for an Angular route
Angular SEO作業用プロンプト
この記事のAngular SEOタスク向けに、そのままコピーできるプロンプトです。説明された入力を貼り、出力を自分の判断で確認してください。機械的な部分の時間は節約しますが、URL検査によるテストの代わりにはなりません。
1. ルートの生HTMLとレンダリングHTMLを比較する
同じURLについて、curl -sL <url>の出力(生HTML)と、URL検査ライブテスト(またはAhrefs/Screaming FrogのJSレンダリングクロール)の「レンダリングHTML」パネルを貼り付けます。
Here is the raw HTML for [URL] (fetched with curl, before JavaScript runs):
[paste raw HTML]
Here is the rendered HTML for the same URL (from Google Search Console URL
Inspection's Live Test, or a JS-rendering crawler):
[paste rendered HTML]
Compare the two. List: (1) content present in rendered but missing from raw —
this is what depends on client-side rendering, (2) any <title>, meta description,
or JSON-LD that differs between the two versions, (3) whether the raw HTML shows
a near-empty <app-root> (a sign of CSR with no SSR/prerendering).CSRに依存している部分と、生HTMLとレンダリング版の間でtitle/meta/schemaがずれている部分を短く一覧にして返すよう求めます。最初に直す価値があるのはこの2点です。
2. Title/Metaサービスの実装を確認する
TitleサービスとMetaサービスを呼び出すAngularのSeoService(または同等のもの)を貼り付けます。
Here is an Angular service that sets page titles and meta tags:
[paste the service's TypeScript code]
Check it against these rules: (1) titles are set via the Title service's
setTitle(), never document.title directly, (2) description is set with
meta.updateTag({ name: 'description', ... }) not addTag() (which can duplicate
the tag on repeat calls), (3) Open Graph tags use the property selector, not
name, (4) nothing in this code reads window/localStorage/document directly in a
way that would break during SSR. Flag any line that violates one of these and
suggest the fix.4つのルールに対して、行ごとの合格/不合格を返し、指摘があれば修正版のスニペットも返すよう求めます。
3. レンダリングモードの誤りについてapp.routes.server.tsを監査する
サーバールートの設定ファイルを貼り付けます。
Here is my Angular app.routes.server.ts, which sets RenderMode per route:
[paste the file]
For each route, tell me: is RenderMode.Prerender used on anything that depends
on per-request or per-user data (a mistake — it would bake stale/wrong data into
the static build)? Is RenderMode.Client used on anything that looks like public,
indexable content (a missed-SEO-opportunity)? Is RenderMode.Server used on fully
static content where Prerender would be faster and cheaper? List each route with
its current mode and whether it matches the decision rule: static data →
Prerender, must-be-fresh → Server, non-indexed internal → Client.ルートが実際に必要とするものとRenderModeが一致しないルートを指摘する、ルートごとの判定を返すよう求めます。
Angular SEOを自分でテストする
Angularアプリをクロール可能かつインデックス可能にするための5つの簡単な質問です。それぞれ答えを選んでから、確認してください。
変更履歴
2026年8月22日に更新。
編集概要と記録された変更の詳細。変更の詳細
-
変更の詳細な注記は現在英語でのみ提供されています。
完全な比較は利用できません — この改訂の以前のスナップショットがアーカイブされていません。
2026年8月6日に更新。
編集概要と記録された変更の詳細。変更の詳細
-
変更の詳細な注記は現在英語でのみ提供されています。
完全な比較は利用できません — この改訂の以前のスナップショットがアーカイブされていません。
2026年7月17日に更新。
編集概要と記録された変更の詳細。変更の詳細
-
変更の詳細な注記は現在英語でのみ提供されています。
-
変更の詳細な注記は現在英語でのみ提供されています。
-
変更の詳細な注記は現在英語でのみ提供されています。
完全な比較は利用できません — この改訂の以前のスナップショットがアーカイブされていません。