SvelteKitデプロイSEO:アダプター、プリレンダリング、エッジレンダリング
SvelteKitのアダプターとルートごとのプリレンダー設定は、ページがどこでいつレンダリングされるかを決定し、それがTTFB、LCP、クロール予算に影響します。デプロイに焦点を当てた深掘り:adapter-static/node/vercel/cloudflare/netlifyの選択、prerender = true/false/'auto'、エッジランタイムの制約、sitemap.xmlとrobots.txtの構築。
言語
SvelteKitのアダプターとルートごとのプリレンダー設定は、ページがどこでいつレンダリングされるかを決定します(ビルド時の静的HTML、サーバー上のSSR、エッジでのSSR)。その決定がTTFBを左右し、それがLCPとクロール容量に影響します。純粋なコンテンツサイトにはadapter-staticを、コンテンツとアプリが混在するサイトにはルートごとのプリレンダーを備えたnode/vercel/cloudflareアダプターを、グローバルなTTFBが重要な場合にはエッジアダプターを選択します(コールドスタートとNode fsの欠如を受け入れる)。prerender = 'auto'は混在サイトのツールです。エッジランタイムはファイルシステムを読み取れません。また、SvelteKitはsitemap.xmlやrobots.txtを生成しないため、それらを+server.jsエンドポイントとして構築し、その戦略はアダプターによって異なります。
TL;DR — SvelteKit はすでにページをサーバー側でレンダリングします。その部分は 処理済みです。このページは次の決定についてです:サイトのビルドとデプロイの方法。アダプターは SvelteKit アプリをホスト(静的ファイルホスト、Node サーバー、または Vercel や Cloudflare などのサービス)向けにパッケージ化し、ページごとのプリレンダー設定は、ページを事前にプレーンな HTML ファイルにするか、訪問のたびに新しくレンダリングするかを決定します。この 2 つの選択が、クローラーが HTML を取得する速度を決定します。また、SvelteKit は sitemap や robots.txt を生成しないため、自分で追加する必要があります。
アダプターとは(平易な言葉で)
すでに SvelteKit サイトを構築したことがあるなら、ブラウザに実際の HTML が送信されることをご存じでしょう。コンテンツは JavaScript が実行される前に存在します。良いことです。これで SEO の難しい問題はすでに解決されています(まだ解決されていない場合は、このセクションの SvelteKit の基礎に関する記事で、レンダリングモードと、最初に避けたい「空のシェル」の罠について説明しています)。
アダプターは、完成した SvelteKit ビルドを取得し、特定のホストが実行できるものに変換する小さなプラグインです。 Evidence for this claim SvelteKit adapters transform a built application for deployment to a particular environment. Scope: SvelteKit adapters. Confidence: high · Verified: SvelteKit: Adapters 同じサイトでも、パッケージは異なります:
adapter-staticはすべてのページをプレーンな HTML ファイルに変換し、一度だけビルドします。ブログ、ドキュメント、訪問者ごとに変わらないマーケティングサイトに最適です。adapter-nodeはアプリを自分で実行する Node.js サーバーにラップします。adapter-vercel、adapter-netlify、adapter-cloudflareは、これらのホスティングサービス向けにパッケージ化し、オンデマンドでページをレンダリングします。場合によっては、訪問者に物理的に近い「エッジ」のサーバーでレンダリングします。
コンテンツはすべての場合で同一です。変わるのは、HTML がいつ作成されるか(事前か、各リクエスト時か)とどこで(単一のサーバーか、グローバルネットワークか)です。
なぜこれが技術的な決定だけでなく SEO の決定なのか
主な点:速度。すでに静的ファイルであるページはほぼ瞬時に読み込まれます。サーバー上でビルドする必要があるページは少し時間がかかります。そして Google は明確に述べています:サイトが*“responds quickly for a while, the limit goes up, meaning more connections can be used to crawl. If the site slows down… the limit goes down and Google crawls less.”* (翻訳) 「サイトがしばらく速く応答すると、制限が上がり、より多くの接続をクロールに使用できます。サイトが遅くなると…制限が下がり、Google のクロールが減ります。」つまり、遅いデプロイはユーザーを苛立たせるだけでなく、Google がサイトの一部しか読まない可能性があります。
決定の簡単なバージョン
- コンテンツサイト(ブログ、ドキュメント、マーケティング)→
adapter-staticを使用し、すべてをプリレンダリングします。可能な限り最速で、壊れるものはありません。 - 動的な部分が少しあるコンテンツサイト(検索、コメント)→ サーバーアダプター(
node/vercel/cloudflare)を使用し、コンテンツページにprerender = trueを設定し、動的な部分はリクエスト時にレンダリングします。 - ログイン済みのパーソナライズされたページがあるアプリまたはダッシュボード → サーバー側でレンダリング(SSR)し、公開のマーケティングページのみプリレンダリングします。
SvelteKit が生成しない 2 つのファイルを忘れないでください
SvelteKit は sitemap.xml や robots.txt を自動的に生成しません。 これらは自分で追加します。通常は小さなエンドポイントファイル(sitemap.xml/+server.js)と、static/ フォルダ内のファイルまたは robots.txt 用の別のエンドポイントとして追加します。 Evidence for this claim SvelteKit can serve static assets from its static directory and create custom responses with +server route files. Scope: Mechanisms for robots.txt and sitemap.xml; files are not generated automatically. Confidence: high · Verified: SvelteKit: Project structure SvelteKit: Routing HTML をレンダリングするほとんどのフレームワークは「完全」に感じられるため、忘れがちです。これら 2 つはそうではありません。
より深いバージョンが必要ですか?各アダプターがレンダリングに何をするか、prerender = 'auto' が混合サイトをどのように処理するか、エッジ関数がファイルを読み取れない理由、アダプターに応じて sitemap 戦略がどのように変わるか。Advanced タブに切り替えてください。
TL;DR — アダプターはSvelteKitが何をレンダリングするかを変えるのではなく、どこで・いつを変えます:ビルド時の静的(
adapter-static)、自分で実行するサーバー上のリクエスト時(adapter-node)、またはサーバーレス/エッジ関数上のリクエスト時(adapter-vercel/-netlify/-cloudflare)。ルートごとのprerender = trueは静的HTMLをビルドし、動的マニフェストからルートを削除します。prerender = 'auto'はプリレンダリングかつマニフェストに保持します——混合/blog/[slug]サイト向けのツールです。エッジランタイムはV8アイソレート上で動作します:Nodefsはなく、コールドスタートはTTFBを悪化させ、それがLCPや(Googleのクロール予算ドキュメントによると)クロール容量に影響します。SvelteKitはsitemap.xmlやrobots.txtを生成しません——+server.jsエンドポイントとしてビルドし、その戦略はアダプターに依存することに注意してください。これは、このセクションのSvelteKit SEO基礎記事の、より狭くデプロイに焦点を当てた補完です。SvelteKitがデフォルトでSSRであることは既にご存知と想定し、ここでは再議論しません。
これらすべてを理解するための核心的な考え方
アダプターは何をレンダリングするかを変えません。どこで・いつを変えるのです。それがすべてです。SvelteKitのドキュメントは正確に述べています:アダプターは*「ビルドされたアプリを入力として受け取り、デプロイ用の出力を生成する」*のです。 Evidence for this claim SvelteKit adapters take the built application as input and generate deployment-specific output. Scope: Deployment output; adapter choice can still constrain supported runtime features. Confidence: high · Verified: SvelteKit: Adapters あなたのコンポーネント、load関数、<svelte:head>メタデータ——すべてのアダプターで同一です。異なるのは:
- いつHTMLが生成されるか:ビルド時(静的/プリレンダリング)またはリクエスト時(サーバー、サーバーレス関数、またはエッジ関数でのSSR)。
- どこで生成されるか:単一のオリジンサーバー上、リージョナルサーバーレス関数上、または訪問者に近いエッジネットワーク上。
以下のすべては、この2つの軸の結果です。
デプロイの選択がSEOの選択である理由
その連鎖は短く、よく文書化されています:TTFB → LCP → クロール容量。
Time to First Byte(最初のバイトまでの時間)は、ホストがレスポンスの送信を開始するまでの時間です。CDNキャッシュから配信されるプリレンダリングされたファイルは、ほぼゼロのTTFBを持ちます。ページをレンダリングしなければならないサーバーは、より高いTTFBを持ちます。コールドスタートするサーバーレスまたはエッジ関数は、最初のヒットではるかに高いTTFBを持つ可能性があります。TTFBはLargest Contentful Paintへの直接的な入力です——受信していないものを描画することはできません——そしてLCPはCore Web Vitalsのシグナルです。
クロール側は、Googleが最も明確に述べている部分です。クロール予算のドキュメントから:“If the site responds quickly for a while, the limit goes up, meaning more connections can be used to crawl. If the site slows down or responds with server errors, the limit goes down and Google crawls less.” (翻訳) 「サイトがしばらく速く応答すると、制限が上がり、クロールに使用できる接続が増えます。サイトが遅くなるかサーバーエラーで応答すると、制限は下がり、Googleのクロールは減ります。」そしてベストプラクティスの一文:“Make your pages efficient to load. If Google can load and render your pages faster, we might be able to read more content from your site.” (翻訳) 「ページを効率的に読み込めるようにしてください。Googleがページをより速く読み込み、レンダリングできれば、サイトからより多くのコンテンツを読むことができるかもしれません。」応答が遅いコールドスタートのエッジ関数も、遅いオリジンサーバーと同じダイナミクスに従います。
最初に正直な注意点を一つ:GoogleはSvelteKit固有のガイダンスを公開していません。 SvelteKitアダプター、prerender = 'auto'、またはエッジのコールドスタートに言及したドキュメントやSearch Off the Recordのエピソードはありません。ここで私がしているのは、Googleの一般的なレンダリングとクロール予算のガイダンスをSvelteKitの具体的なメカニズムに適用することです——SvelteKitについてコメントした担当者を引用しているのではありません。なぜなら、そのような担当者は存在しないからです。“server-side or pre-rendering is still a great idea because it makes your website faster for users and crawlers, and not all bots can run JavaScript” (翻訳) 「サーバーサイドまたはプリレンダリングは、ユーザーとクローラーの両方にとってウェブサイトを高速化し、すべてのボットがJavaScriptを実行できるわけではないため、依然として素晴らしいアイデアです」というGoogleの枠組みが、最も近い公式のアンカーであり、フレームワークに依存しません。
SEOの成果のためにアダプターを選ぶ
adapter-auto — ゼロ設定のデフォルト、そしてその限界
新しいSvelteKitプロジェクトにはadapter-autoが同梱されています。これはプラットフォーム(Vercel、Netlify、Cloudflare Pages、Azure、AWS)を検出し、ビルド時に一致するアダプターをインストールします。出発点としては良いですが、知っておくべき明確な限界があります:adapter-autoはオプションを受け付けません。 { edge: true }、Cloudflareバインディング、Vercel ISR、またはプラットフォーム固有の設定が必要になった瞬間、基盤となるアダプター(adapter-vercel、adapter-cloudflareなど)を直接インストールします。autoは足場として扱い、本番環境の決定としては扱わないでください。
adapter-static — コンテンツ重視サイト向けの完全SSG
adapter-staticはビルド時にサイト全体を静的ファイルにプリレンダリングします。サーバーは実行されず、ホストがフラットなHTMLを配信します。 Evidence for this claim adapter-static prerenders a SvelteKit site as static files. Scope: Routes must be prerenderable; performance outcomes depend on hosting and page design. Confidence: high · Verified: SvelteKit: Static site generation コンテンツ重視のサイトにとって、これは最も強力なSEOプロファイルです — 最低のTTFB、コールドスタートなし、落ちるものもありません。唯一の要件は、基礎記事で詳しく説明されている落とし穴です:ビルド中はSSRをオンにしておく必要があります。そうしないと、レンダリングされたHTMLではなく空のシェルが生成されます。ここでは指摘するだけで、再説明はしません。
欠点は柔軟性の低さです。リクエストごとにサーバーロジックを本当に必要とするもの(真の検索、ユーザーごとのコンテンツ、サードパーティのエンドポイントなしのフォーム処理)は、純粋に静的なビルドでは実現できません — それがまさに次のアダプターの目的です。
adapter-node — 制御可能なサーバー
adapter-nodeはスタンドアロンのNode.jsサーバーを生成します。自分で実行し、スケーリングし、TTFBを管理します。これは最も柔軟なオプションであり、実行時の驚きが最も少ないものです — fsを含む完全なNode APIを利用できます。既にインフラがある場合、エッジランタイムで実行できないNodeライブラリが必要な場合、またはウォームサーバーからの予測可能な(コールドスタートしない)応答時間を望む場合に適しています。トレードオフは運用面です:サーバーを実行しているため、その速度と稼働時間がクロール容量になります。
adapter-vercel — サーバーレス、エッジ、ISR
adapter-vercelはデフォルトでVercelのサーバーレス関数にデプロイし、export const configを介してルートごとにSEO関連のレバーをいくつか設定できます:
runtime: 'edge'はそのルートをVercelのエッジランタイムに移動します(詳細は後述)。regionsはサーバーレス関数の実行場所を制御します — ユーザー(またはデータベース)に近いほどレイテンシが低くなります。isrはインクリメンタル静的再生成を有効にします:isr: { expiration: 60 }はキャッシュされた静的アセットを配信し、ウィンドウ後に再生成します。これにより、“プリレンダリングされたコンテンツのパフォーマンスとコストの利点と、動的にレンダリングされたコンテンツの柔軟性を兼ね備えた” ものが得られます。ISRは純粋な静的と純粋なSSRの間の真の第4の道です — ただし、ドキュメント自身の注意書きに注意してください:“export const prerender = trueのルートでISRを使用しても効果はありません。ルートはビルド時にプリレンダリングされるためです。” ISRとプリレンダリングは代替手段であり、積み重ねるものではありません。
adapter-cloudflare — Workers/Pages、グローバルエッジ
adapter-cloudflareはCloudflare WorkersとPagesをターゲットにします — グローバルエッジネットワークでのSSRで、地理的に分散したオーディエンスにとっては多くの場合最低のTTFBです。重要な制約はランタイムです:WorkersはNodeではなくV8アイソレート上で実行されます。ドキュメントから:“Cloudflare Workersではfsを使用できません。” 一部のNode APIはnodejs_compat互換フラグの背後でのみ機能し、それでもサポートは一対一ではありません。リクエスト時にファイルを読み取っていた場合(リダイレクトマップ、データファイル、カスタムOG画像入力)、そのコードは再考が必要です — これについては以下のエッジセクションで説明します。
(古いadapter-cloudflare-workersは非推奨です。新しいプロジェクトではadapter-cloudflareを使用します。これはWorkersとPagesの両方を処理します。古いものを使っている場合は、移行が推奨されるパスです。)
adapter-netlify — 関数またはエッジ関数(Deno)
adapter-netlify はデフォルトでNetlifyのNodeベースの関数にデプロイし、edge: true を指定するとDenoベースのEdge Functionsにデプロイします。Vercelと同じ構成で、デフォルトはサーバーレス、エッジはオプトインです。SvelteKit固有の注意点が1つあります。Netlify Formsは、デプロイ時にNetlifyがフォームのマークアップを検出できるように、フォームのページをプリレンダリングする必要があります。これは、アダプターの選択に加えて「このルートをプリレンダリングする」という小さな要件です。
各選択肢を一言で
- 純粋なコンテンツサイト →
adapter-staticを使用し、すべてをプリレンダリングします。 - 動的な部分があるコンテンツサイト →
adapter-node/-vercel/-cloudflareを使用し、コンテンツにはprerender = true、動的ルートにはfalse/'auto'を設定します。 - パーソナライズされたアプリ/ダッシュボード → SSR優先(Nodeまたはエッジ)で、静的シェル(マーケティング、ログイン)のみプリレンダリングします。
- グローバルでTTFBが重要なオーディエンス → 動的ルートにはエッジアダプターを使用し、Node APIの制約とコールドスタートの現実を受け入れます。
(Decision Treeタブでは、これを分岐フローとして説明しています。)
混合サイトのプリレンダリング戦略
true / false / 'auto' が実際に行うこと
export const prerender はルート(またはレイアウト)ごとのページオプションであり、3つの値は単なるオン/オフではありません:
true— ビルド時にこのルートを静的HTMLとしてビルドします。重要なのは、*「動的SSRに使用されるマニフェストから除外され、サーバー(またはサーバーレス/エッジ関数)を小さくする」*ことです。一度プリレンダリングすると、そのルートは動的レンダリングにフォールバックできません — 完全に静的です。false— 常にリクエスト時にレンダリングします。静的ファイルはありません。'auto'— 混合サイトのツールです。ルートをプリレンダリングし、動的サーバーマニフェストにも保持するため、同じルートを既知のパスには静的に、それ以外にはサーバーレンダリングで提供できます。これはまさにドキュメントが説明するケースのために作られています:/blog/[slug]のようなルートで、*「最新/人気のコンテンツをプリレンダリングしたいが、ロングテールはサーバーレンダリングしたい」*場合です。
プリレンダリングされたルートはサーバーバンドルを縮小するため、ほとんどプリレンダリングされたサイトに少数の 'auto'/false ルートを追加すると、より小さく、安価で、高速な関数がデプロイされます — SEOとは独立した効率性の利点です。
動的ルートには entries 関数が必要
プリレンダリングクローラーは、エントリーポイントから <a> リンクをたどってページを発見します。これは静的ルートには機能しますが、/blog/[slug] のような動的ルートには、クローラーが見つける固定URLがありません。特定のスラッグにリンクするものがない場合、SvelteKitはその存在を知りません — そして、ルートが*「プリレンダリング可能としてマークされたが、プリレンダリングされなかった」*という古典的なビルドエラーに遭遇します。
修正方法は、パラメータ値を列挙する明示的な entries 関数(または config.kit.prerender.entries)です:
// src/routes/blog/[slug]/+page.server.js
export const prerender = true;
export function entries() {
return [
{ slug: 'hello-world' },
{ slug: 'sveltekit-deployment-seo' },
];
}実際には、そのリストをCMSやコンテンツディレクトリから生成します。これがないと、プリレンダリングはリンククローラーが偶然見つけたスラッグのみをカバーします。
実際の /blog/[slug] パターン
この2つを組み合わせると、標準的な混合サイト構成になります:prerender = 'auto' に加えて、最近の投稿や人気の投稿を返す entries 関数を使用します。これらはビルド時に静的HTMLを取得します。リストにないものはすべて、オンデマンドでSSRにフォールバックします。新しい投稿は、次のビルドでプリレンダリングされるまで動的にレンダリングされます。「毎ビルドで40,000件すべての投稿をプリレンダリングする」と「すべてのリクエストで全投稿をレンダリングする」の中間の実用的な選択肢です。
SEOに影響するエッジランタイムの制約
config.runtime = 'edge' はルートごと(Vercelの場合)
エッジはオールオアナッシングのスイッチではありません。Vercelではルートごとのページオプションです:
// +page.server.js or +server.js
export const config = { runtime: 'edge' };つまり、トラフィックの多いキャッシュ可能なルートをエッジにプッシュして低TTFBを実現しつつ、Node依存のルートは同じデプロイ内の標準サーバーレス(Node)ランタイムに維持できます。意図的に混在させましょう。
fs なし、任意のNode APIなし
エッジランタイム(Cloudflare Workers、Vercel Edge Functions、NetlifyのDeno Edge Functions)は、Nodeのfsを提供しません。Cloudflareのドキュメントには、“You can’t use fs in Cloudflare Workers.” とあります。Vercelのドキュメントには、“You can’t use fs in edge functions.” とあります。どちらも同じ2つの回避策を指しています。$app/serverのreadヘルパーを使用してバンドルされたアセットにアクセスするか、“prerender the routes in question” して、ファイルアクセスをリクエスト時ではなくビルド時に行うかです。
これが問題となるSEO関連のケース:フォントやテンプレートファイルを読み込む動的OG画像生成、ファイルベースのリダイレクトマップ、ディスクからコンテンツを読み込むサイトマップエンドポイントなどです。これらはすべて、$app/serverのread()に移行するか、プリレンダリング/ビルド時に移行します。これはブロッカーではなく、「エッジを選ぶ前に知っておくべき」制約です。
コールドスタートとTTFB — エッジが役立つ場合とそうでない場合
エッジ関数もコールドスタートします。最初のリクエストでのコールドエッジ関数は、ウォームなNodeサーバーよりも遅く、キャッシュから配信されるプリレンダリングされたファイルよりもはるかに遅くなる可能性があります。エッジが勝つのは、関数がウォームな状態を保つか、積極的なキャッシュと組み合わせてほとんどのリクエストが関数に到達しない場合です。これは自動的に最速のオプションというわけではありません。「エッジにデプロイする」は「高速化」の同義語ではありません。コンテンツサイトの場合、プリレンダリングされた静的出力は、起動する関数がないため、TTFBでエッジSSRに常に勝ります。
sitemap.xmlとrobots.txtの生成(SvelteKitは生成しません)
これは、ほとんどのSvelteKitチュートリアルが省略し、ほとんどの監査が指摘するギャップです。SvelteKitは、アダプターやプリレンダリングするページ数に関係なく、sitemap.xmlもrobots.txtも自動的に生成しません。数千のプリレンダリングされたページを持つ完全に静的なサイトでも、自分で構築しない限り、サイトマップなしで配信されます。
+server.jsエンドポイントパターン
慣用的なサイトマップは、正しいContent-TypeでXMLを返すルートエンドポイントです:
// src/routes/sitemap.xml/+server.js
export const prerender = true; // needed on adapter-static
export async function GET() {
const urls = await getAllUrls(); // from your CMS/content
const body = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls.map((u) => ` <url><loc>${u}</loc></url>`).join('\n')}
</urlset>`;
return new Response(body, {
headers: { 'Content-Type': 'application/xml' },
});
}戦略はアダプターによって異なります
この記事全体を結び付ける部分は次のとおりです:サイトマップ戦略は、アダプターの選択によって決まります。
adapter-staticの場合、サイトマップエンドポイントにはexport const prerender = trueが必要で、静的出力に含まれます。実行時にリクエストに応じて生成するサーバーはありません。ビルド時に焼き付けられるため、最後のビルドと同じだけ新鮮です。- Node/サーバーレス/エッジアダプターの場合、同じエンドポイントがCMSやデータベースからサイトマップをリクエストごとに動的に生成できます。常に最新で、再ビルドは不要です。(エッジアダプターでは、
fsの制約を忘れないでください。ディスク読み取りではなく、APIやバインディングからURLを取得します。)
つまり、「サイトマップは静的か動的か?」という質問は、別の決定ではありません。すでに選択したアダプターから導き出されます。
robots.txt:静的ファイルとエンドポイント
2つのオプションがあります。static/フォルダーにプレーンなrobots.txtを置く(/robots.txtで自動的に配信されます)のが最も簡単な選択で、ほとんどのサイトに適しています。または、環境によって異なる必要がある場合(ステージングでクローラーをブロックし、本番で許可するなど)は、src/routes/robots.txt/+server.jsエンドポイントから生成します。どちらの場合も、/_app/バンドルやCSSをブロックしないでください。レンダリングするエンジンのレンダリングが壊れます。
より広いフレームワークやJavaScript SEOの観点からこれに取り組んでいる場合、ここでの「レンダリングがどこでいつ行われるか」のロジックは、一般的なJavaScript SEOを支配するのと同じロジックであり、このセクションのSvelteKitの基礎に関する記事は、この記事が基づいているレンダリングモードとメタデータパターンをカバーしています。
AIまとめ
Advancedバージョンの簡潔な見解:
- アダプターは「どこで」「いつ」を変えるもので、「何を」は変えない。 アダプターは*「ビルド済みアプリを入力として受け取り、デプロイ用の出力を生成する」* — 同じコンテンツ、異なるタイミング(ビルド時 vs リクエスト時)と場所(オリジン vs エッジ)。
- なぜSEO上の判断なのか: TTFB → LCP → クロール容量。Google: サイトが*「迅速に応答すれば…上限は上がる…サイトが遅くなれば…Googleのクロールは減る。」* GoogleのガイダンスでSvelteKitを具体的に挙げているものはない — これは一般的なガイダンスをSvelteKitの仕組みに適用したもの。
- アダプター:
adapter-auto(ゼロ設定、オプションなし);adapter-static(SSG、コンテンツサイト、最低のTTFB);adapter-node(自分で制御するサーバー、完全なNode API);adapter-vercel(サーバーレス + エッジ + ISR);adapter-cloudflare(グローバルエッジWorkers、fsなし;adapter-cloudflare-workersは非推奨);adapter-netlify(FunctionsまたはDeno Edge Functions)。 - プリレンダリング:
trueは静的HTMLを生成し、動的マニフェストからルートを削除;falseは常にSSR;'auto'はプリレンダリングかつ動的を維持 —/blog/[slug]のための混在サイトツール(人気記事はプリレンダリング、ロングテールはSSR)。 - 動的ルートには
entries関数が必要 さもないと*「プリレンダリング可能とマークされたが、プリレンダリングされなかった」*というエラーに遭遇する。 - エッジの制約:
runtime: 'edge'はルートごと(Vercel);fsなし(「Cloudflare Workersではfsを使えない」 / エッジ関数)—$app/serverのread()またはプリレンダリングを使用; コールドスタートにより、エッジはウォームサーバーや静的ファイルより遅くなることがある。 - 組み込みのsitemap/robots.txtはない。
sitemap.xml/+server.jsエンドポイントを構築(adapter-staticではprerender = true; サーバー/エッジアダプターでは動的)。robots.txtはstatic/またはエンドポイント経由。 - ISR ≠ プリレンダープラス: 「
export const prerender = trueのルートでISRを使用しても効果はありません。」 これらは代替手段。
公式ドキュメント
SvelteKitと検索エンジンからの一次情報ドキュメント。
SvelteKit
- アダプター • SvelteKit Docs — 概要: アダプターはビルド済みアプリを受け取り、デプロイ出力を生成する。
- ゼロ設定デプロイ(adapter-auto) • SvelteKit Docs — プラットフォームごとの検出と「オプションを受け取らない」制限。
- Nodeサーバー(adapter-node) • SvelteKit Docs — スタンドアロンのNodeサーバー、環境変数、グレースフルシャットダウン。
- 静的サイト生成(adapter-static) • SvelteKit Docs — サイト全体のSSG、SSR要件、SPAフォールバックのSEO警告。
- Vercel(adapter-vercel) • SvelteKit Docs — ルートごとの
runtime、regions、split、およびインクリメンタル静的再生成。 - Cloudflare(adapter-cloudflare) • SvelteKit Docs — Workers/Pages、
platform.envバインディング、nodejs_compat、およびfsの制限。 - Cloudflare Workers(adapter-cloudflare-workers、非推奨) • SvelteKit Docs — 非推奨のレガシーアダプターと移行パス。
- Netlify(adapter-netlify) • SvelteKit Docs — Node Functions vs. DenoベースのEdge Functions(
edge: true)、およびFormsのプリレンダー要件。 - ページオプション(prerender、ssr、csr、config) • SvelteKit Docs —
prerender = true/false/'auto'、entries関数、およびruntime: 'edge'を含むルートごとのconfig。
- JavaScript SEOの基本を理解する — レンダリングキューと「すべてのボットがJavaScriptを実行できるわけではない」。
- クロール予算を最適化する — 応答速度に紐づくクロール容量; 「ページを効率的に読み込めるようにする」。
Bing / Microsoft
- bingbot シリーズ: JavaScript、動的レンダリング、クローキング。ああ、なんてこった! — Bing のプリレンダリング/動的レンダリングの推奨とクローキングの明確化。
- Microsoft Bing のための高速フロントエンドパフォーマンス — Bing 自身の SSR + CDN/エッジノードアーキテクチャを実世界の証明として。
ソースからの引用
SvelteKit ドキュメント、Google、Bing からの公式声明。各リンクは、ソースページの引用箇所にジャンプするディープリンクです。
SvelteKit ドキュメント — アダプターとページオプション
- “adapter-auto does not take any options.” (翻訳) 「adapter-auto はオプションを受け取りません。」 — ゼロ設定のデフォルトアダプターについて。 引用にジャンプ
prerender = trueについて — プリレンダリングされたルートは “excluded from manifests used for dynamic SSR, making your server (or serverless/edge functions) smaller.” (翻訳) 「動的 SSR に使用されるマニフェストから除外され、サーバー(またはサーバーレス/エッジ関数)を小さくします。」 引用にジャンプ'auto'について —/blog/[slug]のケースで、“prerender your most recent/popular content but server-render the long tail.” (翻訳) 「最新/人気のコンテンツをプリレンダリングし、ロングテールをサーバーレンダリングする」場合。 引用にジャンプ- エッジの
fs制限について — “You can’t use fs in Cloudflare Workers.” (翻訳) 「Cloudflare Workers では fs を使用できません。」 引用にジャンプ - Vercel ISR とプリレンダリングの比較 — “Using ISR on a route with export const prerender = true will have no effect, since the route is prerendered at build time.” (翻訳) 「export const prerender = true のルートで ISR を使用しても効果はありません。ルートはビルド時にプリレンダリングされるためです。」 引用にジャンプ
Google — レンダリングとクロール予算
- “Keep in mind that server-side or pre-rendering is still a great idea because it makes your website faster for users and crawlers, and not all bots can run JavaScript.” (翻訳) 「サーバーサイドまたはプリレンダリングは、ユーザーとクローラーにとってウェブサイトを高速化し、すべてのボットが JavaScript を実行できるわけではないため、依然として優れたアイデアであることに留意してください。」 引用にジャンプ
- “If the site responds quickly for a while, the limit goes up, meaning more connections can be used to crawl. If the site slows down or responds with server errors, the limit goes down and Google crawls less.” (翻訳) 「サイトがしばらく速く応答すると、制限が上がり、クロールに使用できる接続が増えます。サイトが遅くなったり、サーバーエラーで応答したりすると、制限が下がり、Google のクロールが減ります。」 引用にジャンプ
- “Make your pages efficient to load. If Google can load and render your pages faster, we might be able to read more content from your site.” (翻訳) 「ページを効率的に読み込めるようにしてください。Google がページをより速く読み込んでレンダリングできれば、サイトからより多くのコンテンツを読み取れる可能性があります。」 引用にジャンプ
Bing — プリレンダリングと独自のエッジアーキテクチャ
- “We encourage detecting our bingbot user agent, prerendering the content on the server side and outputting static HTML for such sites…” (翻訳) 「当社の bingbot ユーザーエージェントを検出し、サーバー側でコンテンツをプリレンダリングして、そのようなサイトには静的 HTML を出力することを推奨します…」 — Fabrice Canel & Frédéric Dubut、Microsoft Bing。 引用にジャンプ
- “User traffic routes first to the closest CDN node (called an ‘edge node’).” (翻訳) 「ユーザートラフィックは、最初に最も近い CDN ノード(「エッジノード」と呼ばれる)にルーティングされます。」 — Bing Search Quality Insights、Bing 自身の SSR + エッジアーキテクチャについて。 引用にジャンプ
SvelteKitデプロイSEOチェックリスト
アダプター、プリレンダリング、サイトマップの設定がクローラーに悪影響を与えないことを確認するためのパス:
- 設定が必要な場合(エッジ、ISR、バインディング)は、
adapter-autoから明示的なアダプターに移行済みである。 - アダプターがサイトタイプと一致している — 純粋なコンテンツには
adapter-static、リクエストごとのロジックがあるものにはサーバー/エッジアダプター。 - コンテンツルートは
prerender = true(または'auto') である。真に動的なルートのみがSSRに残されている。 - 混合動的ルート (
/blog/[slug]) はprerender = 'auto'と、既知のパスを列挙するentries関数を使用している。 - 未解決の “marked as prerenderable, but were not prerendered” ビルドエラーがない。
- いずれかのルートが
runtime: 'edge'を使用する場合、Nodeのfsを呼び出さない — ファイルアクセスは$app/serverのread()を使用するか、プリレンダリングされる。 - エッジ/サーバーレスでの コールドスタート を考慮している — TTFBが重要な場所ではキャッシュ可能、静的、またはウォームである。
- sitemap.xml エンドポイントが存在する (
adapter-staticではprerender = true、サーバー/エッジアダプターでは動的)。 - robots.txt が存在し (
static/内または+server.jsエンドポイントとして)、/_app/やCSSをブロックして いない。 -
prerender = trueルートに ISR を重ねようと していない (効果がない)。 - GSC URL InspectionとPageSpeed Insightsで レンダリングされたHTMLとレスポンス速度 を確認済みである。
メンタルモデル
1. 何を、ではなく、どこで、いつ。 アダプターはコンテンツを変更することはありません — HTMLが作成される タイミング (ビルド時 vs. リクエスト時) と 場所 (オリジン vs. エッジ) を変更します。すべてのデプロイSEOの質問は、この2つの軸に帰着します。設定に触れる前にこれらを問いかけてください。
2. プリレンダリングはルートをサーバーから削除する。
prerender = true は単なる「静的化」ではありません — 動的マニフェストからルートを 取り除きます。これにより関数が縮小され、動的フォールバックが排除されます。'auto' は例外です: プリレンダリング かつ マニフェストに残ります。
3. /blog/[slug] の分割。
実際のコンテンツサイトのデフォルトパターン: 名前を付けられるエントリ (entries 関数が最近/人気を返す) をプリレンダリングし、ロングテールをSSRします。'auto' は両方を同時に実現するスイッチです。
4. エッジはトレードであり、アップグレードではない。
エッジは地理的近接性 (ウォーム時の低TTFB) をもたらし、Node API (fs なし) とコールドスタートのリスクを犠牲にします。ウォームなNodeサーバーに勝るのは一部の場合のみで、TTFBではプリレンダリングされた静的出力に常に負けます。デフォルトではなく、理由があって選択してください。
5. サイトマップはアダプターに従う。
「静的サイトマップか動的サイトマップか?」は独立した決定ではありません。adapter-static → プリレンダリングされたサイトマップ、ビルド時のみ新鮮。サーバー/エッジアダプター → リクエストごとのサイトマップ、常に最新。アダプターがすでに答えを出しています。
6. 2つのファイルを生成するものはない。 SvelteKitはどのアダプターでもsitemap.xmlもrobots.txtも作成しません。自分で書いていなければ、存在しません。これを起動チェックリストに組み込んでください。
どのアダプター+プリレンダリングの組み合わせを選ぶべきか?
SvelteKitデプロイにおける中核的な「どの道を選ぶか?」という質問は、このサイトはどのように配信されるべきか? です。サイトをこれに沿って確認してください:
1. リクエストごとのサーバーロジック — 認証、パーソナライゼーション、ライブ検索、フォーム処理、ユーザーごとのデータ — を必要とするページはありますか? → いいえ (すべての訪問者に対してすべてのページが同じ): 2へ。 → はい: 3へスキップ。
2. 純粋なコンテンツサイト(ブログ、ドキュメント、マーケティング)。
→ adapter-static を使用し、サイト全体(またはルート
レイアウト)で prerender = true を設定します。プリレンダリングされた sitemap.xml/+server.js(prerender = true)と
static/robots.txt を追加します。TTFB が最も低く、コールドスタートなし、実行するものもありません。ここで完了です。
3. サイト全体が動的ですか、それとも一部のルートだけですか? → 一部のルートのみ(ほとんどがコンテンツ、動的な部分が少しある): 4 へ進みます。 → ほとんど/完全に動的(アプリ、ダッシュボード、ユーザーごとのデータを持つ Eコマース): 5 へ 進みます。
4. 動的な部分があるコンテンツサイト。
→ サーバー/エッジアダプター(adapter-node、-vercel、または
-cloudflare)を使用します。コンテンツルートは prerender = true、動的ルートは
false に設定します。既知の人気コンテンツがある /blog/[slug] スタイルのルートでは、
prerender = 'auto' + entries 関数を使用します。サイトマップは CMS から動的に
生成します。完了です。
5. アプリ / ダッシュボード / Eコマース(SSR ファースト)。 ここで SSR をどこで実行するかを選択します:
→ 予測可能なレイテンシ、Node ライブラリ、インフラがある場合: adapter-node
(ウォームサーバー、完全な Node API、コールドスタートの驚きなし)。
→ グローバルなオーディエンス、TTFB が最も重要、重い Node 依存関係がない場合: エッジアダプター
(adapter-cloudflare、またはルートごとに runtime: 'edge' を指定した adapter-vercel)—
fs なし($app/server の read() またはプリレンダリングを使用)とコールドスタートを受け入れます。
本当に静的なシェル(マーケティング、ログイン)のみをプリレンダリングします。
4 番目のパス(Vercel のみ): ルートが「ほとんど静的だが、たまに
変更される」場合は、prerender = true の代わりに ISR(isr: { expiration })を検討してください
— 両方は決して使用しないでください。「export const prerender = true を持つルートでの ISR は
効果がありません。」
サイトマップは静的または動的のどちらにすべきですか?
adapter-static を使用していますか? → 静的/プリレンダリングされたサイトマップエンドポイント
(prerender = true)。ビルド時に焼き付けられます。公開時に再ビルドするサイトに適しています。
Node/サーバーレス/エッジアダプターを使用していますか? → リクエストごとに CMS/DB から生成される動的サイトマップ — 常に最新で、再ビルドは不要です。(エッジでは、ディスクの fs 読み取りではなく、API または
バインディングから URL を取得します。)
絶対にしないこと: 「ページがすべて静的だから」という理由でサイトマップを提供しないこと。静的出力と サイトマップの発見可能性は無関係です — SvelteKit はどのアダプターでもどちらのファイルも生成しません。
SvelteKit デプロイ SEO — チートシート
アダプターの概要
| アダプター | レンダリング | ランタイム | SEO メモ |
|---|---|---|---|
adapter-static | ビルド時(SSG) | なし | TTFB が最も低く、コールドスタートなし。コンテンツサイト向け |
adapter-node | リクエスト時(SSR) | Node | 完全な Node API(fs ✅)。サーバーを自分で実行 |
adapter-vercel | リクエスト時 | サーバーレス / エッジ | ルートごとの runtime、regions、ISR |
adapter-cloudflare | リクエスト時 | V8 エッジ | グローバルエッジ。fs なし。nodejs_compat |
adapter-netlify | リクエスト時 | Node / Deno エッジ | Deno Edge Functions には edge: true |
adapter-auto | (上記を検出) | — | オプションを受け付けません — スキャフォールドのみ |
プリレンダリング値
| 値 | 静的 HTML? | 動的マニフェスト内? | 使用目的 |
|---|---|---|---|
true | ✅ | ❌(削除) | 既知の静的コンテンツルート |
false | ❌ | ✅ | 真に動的なルート |
'auto' | ✅ | ✅ | /blog/[slug] — 人気のあるものをプリレンダリング、ロングテールは SSR |
クイックルール
- 動的プリレンダリングルート →
entries関数を追加します(または 「プリレンダリングされていません」エラーに直面します)。 runtime: 'edge'はルートごと(Vercel)— エッジと Node ルートを混在させます。- エッジ =
fsなし →$app/serverのread()またはプリレンダリングを使用します。 - コールドスタートにより、最初のヒットではエッジはウォームサーバー / 静的ファイルよりも遅くなります。
- ISR ≠ プリレンダリング —
prerender = trueルートのisrは何も行いません。 - 自動サイトマップ/robots.txt なし — 両方を作成します。
adapter-staticの場合はサイトマップ エンドポイントでprerender = true。サーバー/エッジでは動的。 - robots.txt で
/_app/または CSS をブロックしないでください。
プリレンダリングされたルートがデプロイメントにありません
考えられる原因: クローラーがパスを発見できなかった、entries の値が存在しない、または事前レンダリングが失敗した。修正方法: クロール可能なリンクまたは明示的なエントリを追加し、ビルド警告をリリース失敗として扱う。確認方法: 出力マニフェストにルートが含まれ、本番環境が完全なHTMLを返すことを確認する。
adapter-static が動的ルートで失敗する
考えられる原因: ルートをビルド時に完全に列挙できない。修正方法: 有限のエントリを指定するか、ルートを再設計するか、そのパスにはサーバー対応アダプターを使用する。確認方法: 選択したアダプターがビルドされ、代表的な各ルートが意図したレスポンスを返すことを確認する。
エッジデプロイでファイルシステムまたはNode APIエラーが発生する
考えられる原因: ルートコードまたは依存関係が、エッジランタイムで利用できないNode機能を前提としている。修正方法: 依存関係を置き換えるか、処理を互換性のあるサービスに移すか、Nodeアダプターを選択する。確認方法: 本番環境のSSRがランタイム例外なしで成功することを確認する。
サイトマップまたはrobots.txtがHTMLを返す
考えられる原因: フォールバックルートがエンドポイントをキャッチするか、+server ハンドラーが誤ったボディ/ヘッダーを設定している。修正方法: 正しいコンテンツタイプを持つ明示的なエンドポイントハンドラーを作成する。確認方法: 直接リクエストが期待どおりのtext/XMLレスポンスと 200 ステータスを返すことを確認する。
事前レンダリングルートとSSRルートでメタデータが異なる
考えられる原因: headデータが異なるコードパスで読み込まれるか、ブラウザの状態に依存している。修正方法: サーバー/ビルドセーフなページデータからメタデータ生成を一元化する。確認方法: 両方のルートタイプの生のHTMLに、同等のタイトル、正規化、robotsロジックが含まれていることを確認する。
アダプターが実際に何を出荷したかを確認する
アダプターを選択して事前レンダリングする目的は、クローラーが高速で完全なHTMLを取得することです。これらのチェックは、それが実際に起こったことを確認します — ブラウザではなく、生のレスポンスからです。
ページは事前レンダリング/SSRされていますか(生のHTMLにコンテンツがあるか)?
素の curl はJavaScriptを実行しないため、レンダリングしないクローラーが正確に何を見るかを確認できます。
macOS / Linux
# Raw HTML as the host sends it (no JS executed)
curl -sL "https://example.com/your-page/" -o raw.html
grep -o "Your unique headline text" raw.html # empty = CSR shell, not prerenderedWindows (PowerShell)
Invoke-WebRequest -Uri "https://example.com/your-page/" -OutFile raw.html
Select-String -Path raw.html -Pattern "Your unique headline text"TTFBは高速ですか(それとも関数がコールドスタートしていますか)?
TTFBはLCPとクロール容量に影響するため、測定してください。URLにコールドでアクセスし、次にウォームでアクセスします:
# Time to first byte, twice — a big first number then a small one = cold start
for i in 1 2; do
curl -s -o /dev/null -w "TTFB: %{time_starttransfer}s\n" "https://example.com/your-page/"
done事前レンダリング/静的ページは一貫して低いはずです。最初の値が大きく、2回目のアクセスで低下する場合は、典型的なサーバーレス/エッジのコールドスタートです。
このルートは事前レンダリングされたか、動的に配信されたか?
静的ホストとCDNは通常、ヘッダー(キャッシュステータス、age、x-vercel-cache、cf-cache-status)でそれを明らかにします:
curl -sI "https://example.com/your-page/" | grep -iE "cache|age|x-vercel|cf-"HIT(またはゼロ以外の age)は、キャッシュ/事前レンダリングされたコンテンツが配信されていることを意味します。リクエストごとに MISS/DYNAMIC が返る場合は、リクエストごとにレンダリングされていることを意味します。
サイトマップは実際に存在し、XMLを返しますか?
SvelteKitはサイトマップを生成しないため、正しいコンテンツタイプで実際に存在することを確認してください:
curl -sI "https://example.com/sitemap.xml" | grep -iE "HTTP/|content-type"
# Want: 200 + content-type: application/xml (not text/html or a 404)DevToolsコンソールのワンライナー
ブラウザコンソールに貼り付けて、レンダリングされたDOMをクローラーが必要とするものと比較します — 見出しがここにあるのに上記の curl 出力にない場合は、クライアントレンダリングされています:
// Is the content in the DOM, and does the sitemap resolve?
console.log('headline in DOM:', document.body.innerText.includes('Your unique headline text'));
fetch('/sitemap.xml').then(r => console.log('sitemap status:', r.status, r.headers.get('content-type')));robots.txtがバンドルをブロックしていないか確認する
curl -sL "https://example.com/robots.txt" | grep -iE "disallow.*(/_app|\.js|\.css)"/_app/(SvelteKitのバンドル出力)またはCSSに一致する Disallow があると、エンジンがページをレンダリングできません — ほとんどの場合、これは誤りです。
SvelteKitデプロイSEOのデバッグツール
- URL Inspection (Google Search Console) — 信頼できる情報源。URL をライブテストし、レンダリングされた HTML、スクリーンショット、ページリソース を確認して、コンテンツとメタデータが存在し、ブロックされていないことを確認します。
- PageSpeed Insights — SvelteKit ドキュメント自身が推奨するツール。TTFB と、アダプターの選択が最も影響する Core Web Vitals(LCP/INP/CLS)を明らかにします。
- WebPageTest — ウォーターフォールとフィルムストリップで、エッジ/サーバーレスデプロイの TTFB とコールドスタート時間を診断します。
curl -w "%{time_starttransfer}"— 最も手軽な生の TTFB とコールドスタートのチェック方法(スクリプトタブを参照)。- ホストダッシュボード(Vercel / Cloudflare / Netlify Analytics) — ルートごとの関数呼び出し回数、コールドスタート率、キャッシュヒット率。エッジ/サーバーレスが実際にあなたにとって高速かどうかの真実を示します。
- Screaming Frog SEO Spider — JS レンダリングのオン/オフでクロールし、サイト全体の生の HTML とレンダリングされた HTML を比較して、プリレンダリングされたルートが完全であることを確認します。
- Ahrefs Site Audit — 欠落またはブロックされたサイトマップ、リダイレクトチェーン、壊れた正規化、インデックス可能性の問題を大規模に明らかにします。
自分で試す: SvelteKit デプロイ SEO
SvelteKit のアダプター、プリレンダリング、エッジレンダリングに関する 5 つの簡単な質問。各質問に回答を選び、確認してください。
時間をかける価値のあるリソース
関連する私の記事
- JavaScript SEO: A Definitive Guide — ここでのすべてのアダプター決定の基盤となるレンダリングモード(SSR、静的レンダリング、プリレンダリング、CSR の落とし穴)に関する完全なリファレンス。そこで述べたように、あらゆる種類の SSR、静的レンダリング、またはプリレンダリングの設定は検索エンジンにとって問題ない — これがまさにこれらのアダプター選択の背後にある安全網です。
- The Beginner’s Guide to Technical SEO — レンダリング、クロール、Core Web Vitals が全体像の中でどこに位置するか。
私の講演
- How Search Works(SlideShare)— クロール、レンダリング、インデックス、ランキングの解説。TTFB とレンダリングタイミングが重要である理由の背景です。(私の常套的な免責事項が適用されます: “This is my understanding of systems… not going to be 100% complete or accurate.” (翻訳) 「これは私のシステム理解であり…100% 完全または正確ではありません。」)
業界からの情報
- Adapters • SvelteKit Docs — すべての公式アダプターと、
svelte.config.jsでの指定方法に関する権威ある概要。 - Page options (prerender, ssr, csr, config) • SvelteKit Docs — ルートごとの
prerender値、entries関数、runtime: 'edge'を含むルートごとのconfigをチーム自身の言葉で説明。 - Vercel (adapter-vercel) • SvelteKit Docs — エッジランタイム、リージョン、ISR とプリレンダリングの注意点。
- Cloudflare (adapter-cloudflare) • SvelteKit Docs — Workers/Pages デプロイ、バインディング、
fsの制限。 - SvelteKit • Cloudflare Pages docs — Cloudflare 側からのデプロイの仕組みと
platformバインディング。 - SvelteKit SEO: Your Secret Weapon(Okupter)— プリレンダリング、メタタグ、
+server.jsサイトマップ/RSS パターンに関する実践者向けガイド。 - A Deep Dive into SvelteKit’s Rendering Techniques(This Dot Labs)— SSR/SSG/CSR の仕組みとルート/レイアウトごとの設定、“SSR can be expensive” のサーバーロードのトレードオフ。
- Understand JavaScript SEO Basics(Google Search Central)— レンダリングキューと “not all bots can run JavaScript”、すべてのアダプター選択の根底にある一般的なガイダンス。