SvelteKitデプロイSEO:アダプター、プリレンダリング、エッジレンダリング

SvelteKitのアダプターとルートごとのプリレンダー設定は、ページがどこでいつレンダリングされるかを決定し、それがTTFB、LCP、クロール予算に影響します。デプロイに焦点を当てた深掘り:adapter-static/node/vercel/cloudflare/netlifyの選択、prerender = true/false/'auto'、エッジランタイムの制約、sitemap.xmlとrobots.txtの構築。

初回公開:2026年7月3日 · 最終更新:2026年8月3日 · Advanced
言語

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が何をレンダリングするかを変えるのではなく、どこでいつを変えます:ビルド時の静的(adapter-static)、自分で実行するサーバー上のリクエスト時(adapter-node)、またはサーバーレス/エッジ関数上のリクエスト時(adapter-vercel/-netlify/-cloudflare)。ルートごとのprerender = trueは静的HTMLをビルドし、動的マニフェストからルートを削除します。prerender = 'auto'はプリレンダリングかつマニフェストに保持します——混合/blog/[slug]サイト向けのツールです。エッジランタイムはV8アイソレート上で動作します:Node fsはなく、コールドスタートは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-verceladapter-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/serverreadヘルパーを使用してバンドルされたアセットにアクセスするか、“prerender the routes in question” して、ファイルアクセスをリクエスト時ではなくビルド時に行うかです。

これが問題となるSEO関連のケース:フォントやテンプレートファイルを読み込む動的OG画像生成、ファイルベースのリダイレクトマップ、ディスクからコンテンツを読み込むサイトマップエンドポイントなどです。これらはすべて、$app/serverread()に移行するか、プリレンダリング/ビルド時に移行します。これはブロッカーではなく、「エッジを選ぶ前に知っておくべき」制約です。

コールドスタートと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の基礎に関する記事は、この記事が基づいているレンダリングモードとメタデータパターンをカバーしています。

Add an expert note

Pin an expert quote

New person? Create their unclaimed profile at /admin/experts/ → Pin a quote first.