プラットフォーム別デプロイガイド

SvelteKit のデプロイは 「どのプラットフォームに乗せるか」「どのアダプターを使うか」 の 2 軸で決まります。本ページでは主要プラットフォームを横並びで比較し、本サイト自身を含む 4 つのケーススタディで「実際にどう設定するか」を示します。

実行環境の詳細は別ページへ

「Node / Edge / Workers の違いは何か」「platform オブジェクトには何が入るか」など、実行環境そのものの解説は 実行環境とランタイム を参照してください。本ページは プラットフォーム選択 に絞ります。

プラットフォーム選択フロー

「何を作りたいか」から逆引きで決めるフローチャートです。

「静的のみで足りる」場合は迷わず adapter-static。動的処理が必要なら、エッジ実行を要件にするか、自前サーバーを持てるかで分岐します。

プラットフォーム比較表

プラットフォーム推奨アダプター静的 / SSRエッジコールドスタート無料枠向いている用途
GitHub Pagesadapter-static静的のみ✅ public 無制限OSS ドキュメント、本サイトのような学習教材
Cloudflare Pagesadapter-cloudflare静的 + SSR(Functions)ほぼゼロ✅ 500 ビルド/月国際配信、低レイテンシ要件
Verceladapter-auto / adapter-vercel静的 + SSR + ISR + Edgeほぼゼロ✅ Hobby プランプロトタイプ〜中規模 SaaS
Netlifyadapter-auto / adapter-netlify静的 + SSR + Edgeほぼゼロ✅ Starter プランJAMstack、フォーム連携
adapter-node (VPS/Fly.io/Render)adapter-nodeSSR中〜小プラットフォーム依存フルコントロール、長時間 WebSocket
Docker + 任意 PaaSadapter-nodeSSRプラットフォーム依存エンタープライズ、自前 K8s
`adapter-auto` は何を選ぶか

@sveltejs/adapter-autoビルド環境変数を見て Vercel/Netlify/Cloudflare Pages/Cloudflare Workers を自動判別し、対応するアダプターをインストールします。本番に対応プラットフォームが確定しているなら、明示的に adapter-vercel 等を package.jsondevDependencies に書く方が CI が速く、トラブルシュートも容易です。

アダプター選択ロジック

ホスティング先が決まったら、対応するアダプターを svelte.config.js で指定します。

// svelte.config.js
import adapter from '@sveltejs/adapter-static';   // または -vercel / -cloudflare / -netlify / -node

export default {
  kit: {
    adapter: adapter({
      // アダプター固有のオプション
    })
  }
};

主要アダプターの選択ポイントは次のとおりです。

アダプターパッケージレンダリングプラットフォーム
adapter-static@sveltejs/adapter-staticプリレンダリングのみ(SSG)GitHub Pages、S3、CDN、社内静的ホスト
adapter-vercel@sveltejs/adapter-vercelSSR(Node/Edge)、ISR、Image OptimizationVercel
adapter-cloudflare@sveltejs/adapter-cloudflareSSR(Workers)、KV/D1/R2 連携Cloudflare Pages / Workers Static Assets
adapter-netlify@sveltejs/adapter-netlifySSR(Functions/Edge Functions)Netlify
adapter-node@sveltejs/adapter-nodeSSR(任意の Node 環境)VPS、Fly.io、Render、Docker
adapter-auto@sveltejs/adapter-auto上記の自動判別Vercel/Netlify/Cloudflare
`adapter-cloudflare-workers` は廃止

@sveltejs/adapter-cloudflare-workerswrangler.toml 必須)は廃止され、@sveltejs/adapter-cloudflare が Pages と Workers Static Assets の両方をカバーします。Workers Static Assets を使う場合も adapter-cloudflare を選んでください。

ケーススタディ 1: 本サイト(adapter-static + GitHub Pages)

本サイトは adapter-static で全ページプリレンダリングして GitHub Pages に配信 しています。ハイライトは「サブパス配下(/Svelte-and-SvelteKit-with-TypeScript/)」「PWA 対応」「sitemap の lastmod を git ログから取る」の 3 点です。

svelte.config.js

import adapter from '@sveltejs/adapter-static';

export default {
  kit: {
    adapter: adapter({
      pages: 'dist',
      assets: 'dist',
      fallback: '404.html',
      precompress: false,
      strict: false
    }),
    paths: {
      // 本番デプロイ時は GitHub Actions が BASE_PATH=/Svelte-and-SvelteKit-with-TypeScript を渡す
      base: process.env.BASE_PATH ?? '',
      // PWA 対応: navigateFallback で同一の index.html がどのパスからでも返されても
      // asset 解決が破綻しないよう、絶対パス(base 起点)に揃える
      relative: false
    },
    prerender: {
      entries: ['*'],
      crawl: true,
      handleMissingId: 'warn',
      handleHttpError: 'warn'
    }
  }
};

ポイントは以下のとおりです。

  • fallback: '404.html': GitHub Pages のサブパスで存在しない URL に対して 404.html を返す。SPA フォールバック用途にもなる。
  • base: process.env.BASE_PATH ?? '': dev/ローカルビルドでは未設定(サブパスなし)、本番では /Svelte-and-SvelteKit-with-TypeScript
  • relative: false: SvelteKit 2.x デフォルトの relative: true は各 HTML が ../../ 相対パスを持つ。PWA の navigateFallback で別パスから index.html が返された瞬間に asset 404 になるため、false で絶対パスに揃える。
  • prerender.entries: ['*']: 全ページをプリレンダリング。crawl: true で内部リンクをたどって発見されたページも対象に。

.github/workflows/deploy.yml(要点抜粋)

name: Deploy to GitHub Pages

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: false

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0   # sitemap.xml の lastmod を git log から取るため全履歴が必要

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci
      - run: npm run build
        env:
          BASE_PATH: '/Svelte-and-SvelteKit-with-TypeScript'

      - uses: actions/upload-pages-artifact@v5
        with:
          path: ./dist
          include-hidden-files: true   # SvelteKit が生成する .nojekyll を落とさないため必須

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
    steps:
      - id: deployment
        uses: actions/deploy-pages@v5

ここで重要な 2 つのハマりどころ。

GitHub Pages デプロイ時の必須設定
  1. fetch-depth: 0: 本サイトは sitemap.xmllastmodgit log -1 --format=%cI から取得しているため、shallow clone(デフォルト depth=1)だと最新コミット日時しか得られず全ページが同日になる。
  2. include-hidden-files: true: SvelteKit の adapter-staticdist/.nojekyll を出力する(Jekyll の _ 始まりディレクトリ無視を回避するため)。actions/upload-pages-artifact@v3 以降はデフォルトで隠しファイルを除外するため明示が必要。これを忘れると _app/ 配下が 404 になる。

カスタムドメイン

static/CNAME ファイルを置けば GitHub Pages がカスタムドメインを認識します。HTTPS は Repository Settings → Pages → “Enforce HTTPS” で有効化(Let’s Encrypt 自動発行)。

ケーススタディ 2: Vercel(adapter-vercel + ISR)

Vercel は SvelteKit 開発元の Vercel 社が提供するため、最も摩擦が少ないプラットフォームです。

// svelte.config.js
import adapter from '@sveltejs/adapter-vercel';

export default {
  kit: {
    adapter: adapter({
      runtime: 'nodejs22.x',   // 'nodejs20.x' / 'nodejs22.x' / 'edge'
      regions: ['hnd1'],       // 東京リージョン優先
      isr: {
        expiration: 60,        // ISR 60 秒
        bypassToken: process.env.VERCEL_ISR_BYPASS_TOKEN
      },
      images: {
        sizes: [640, 828, 1200, 1920],
        formats: ['image/avif', 'image/webp'],
        minimumCacheTTL: 300,
        domains: ['cdn.example.com']
      }
    })
  }
};
`runtime` オプションは deprecated 方向

Vercel の runtime オプションは個別ルートごとに export const config = { runtime: 'edge' } を書く方式へ移行が推奨されています。グローバル指定は今も動きますが、将来的に廃止される可能性があるため、新規プロジェクトではルート別に書く方が安全です。

環境変数は Vercel Dashboard → Settings → Environment Variables で設定。$env/static/private 経由でアクセスする場合は ビルド時に値を埋め込む ため、変更後は再デプロイが必要です。動的に変えたい値は $env/dynamic/private を使います。

ケーススタディ 3: Cloudflare Pages(adapter-cloudflare)

低レイテンシ・グローバル配信を要件にするなら Cloudflare Pages。

// svelte.config.js
import adapter from '@sveltejs/adapter-cloudflare';

export default {
  kit: {
    adapter: adapter({
      routes: {
        include: ['/*'],
        exclude: ['<all>']
      },
      platformProxy: {
        configPath: 'wrangler.toml',
        environment: undefined,
        experimentalJsonConfig: false,
        persist: true
      }
    })
  }
};

wrangler.toml で KV/D1/R2 をバインドします。

name = "my-sveltekit-app"
pages_build_output_dir = ".svelte-kit/cloudflare"
compatibility_date = "2026-05-01"
compatibility_flags = ["nodejs_compat"]   # Node.js API 互換性

[[kv_namespaces]]
binding = "MY_KV"
id = "..."

[[d1_databases]]
binding = "MY_DB"
database_name = "my-db"
database_id = "..."

ハンドラ内でのアクセスは platform.env 経由。

// +page.server.ts
export const load = async ({ platform }) => {
  const value = await platform.env.MY_KV.get('key');
  const result = await platform.env.MY_DB.prepare('SELECT * FROM users').all();
  return { value, users: result.results };
};
Workers Static Assets との関係

Cloudflare の Pages と Workers Static Assets は機能統合が進んでおり、adapter-cloudflare がどちらでも動作します。wrangler.tomlpages_build_output_dir を使えば Pages、assets.directory を使えば Workers Static Assets として扱われます。

ケーススタディ 4: adapter-node + Docker

自前で Node サーバーを動かしたい、長時間 WebSocket 接続が必要、エンタープライズで K8s に乗せたい、というケース。

// svelte.config.js
import adapter from '@sveltejs/adapter-node';

export default {
  kit: {
    adapter: adapter({
      out: 'build',
      precompress: true,        // gzip/brotli 事前圧縮
      envPrefix: 'MY_'          // 環境変数プレフィックス
    })
  }
};

Dockerfile は多段ビルドで本番イメージを最小化します。

# --- Build stage ---
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build && npm prune --omit=dev

# --- Runtime stage ---
FROM node:22-alpine AS runtime
WORKDIR /app
COPY --from=builder /app/build ./build
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json

ENV NODE_ENV=production
ENV PORT=3000
EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/healthz || exit 1

CMD ["node", "build"]

adapter-node 5.x は sveltekit:shutdown イベントで graceful shutdown が可能です。

// hooks.server.ts
process.on('sveltekit:shutdown', async (reason) => {
  console.log('Shutting down:', reason);
  // データベース切断、キュー drain など
  await db.disconnect();
});

環境変数とシークレット管理

$env/* モジュールの使い分けが重要です。

モジュールタイミングサーバー専用用途
$env/static/privateビルド時に埋め込みDB 接続文字列、API キー(変更頻度低)
$env/static/publicビルド時に埋め込み❌(クライアント露出可)公開可の設定値(PUBLIC_ プレフィックス必須)
$env/dynamic/privateランタイム読み込み環境ごとに変えたいシークレット
$env/dynamic/publicランタイム読み込みPUBLIC_ プレフィックス必須、SSR で動的注入
import { DATABASE_URL } from '$env/static/private';        // ビルド時に固定
import { env } from '$env/dynamic/private';                 // ランタイムで読む
const apiKey = env.STRIPE_SECRET_KEY;

プラットフォーム別のシークレット設定先:

  • Vercel: Dashboard → Settings → Environment Variables(Production/Preview/Development 別)
  • Cloudflare: wrangler secret put SECRET_NAME または Dashboard
  • Netlify: Site settings → Environment variables
  • GitHub Actions: Repository Settings → Secrets and variables → Actions
`PUBLIC_` プレフィックスを忘れない

$env/static/public / $env/dynamic/public で読む変数は 必ず PUBLIC_ プレフィックス(または kit.env.publicPrefix で設定したプレフィックス)が必要です。これがないと SvelteKit がエラーを出します。デフォルトでは private 側は何でも読めますが、kit.env.privatePrefix で制限可能。

CI/CD パイプライン

GitHub Actions の基本テンプレートを再掲します。プラットフォーム別の deploy ステップは差し替えてください。

name: CI/CD
on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v4
        with: { node-version: '22', cache: 'npm' }
      - run: npm ci
      - run: npm run check          # svelte-check
      - run: npm run lint           # eslint
      - run: npm run test           # vitest
      - run: npm run build

  deploy:
    needs: test
    if: github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      # プラットフォーム別 deploy ステップ:
      # - Vercel: vercel/action / `vercel --prod`
      # - Cloudflare: cloudflare/wrangler-action
      # - GitHub Pages: actions/deploy-pages
      # - Netlify: nwtgck/actions-netlify

プレビューデプロイ(PR 単位の検証環境)は Vercel/Netlify では標準機能。Cloudflare Pages も自動でブランチごとのプレビュー URL を発行します。adapter-static + GitHub Pages では非対応のため、別途プレビュー用リポジトリ or Surge.sh 等の併用が必要です。

ベストプラクティス

リリース前のチェックリスト。

  • adapter-auto を使うか明示アダプターか決定: 本番プラットフォーム確定なら明示
  • PUBLIC_ プレフィックス の徹底(漏洩リスク排除)
  • CSP / セキュリティヘッダーセキュリティ対策handle フック設定
  • モニタリング: Sentry / OpenTelemetry / RUM は モニタリング を参照
  • 画像最適化: @sveltejs/enhanced-img または Vercel Image Optimization
  • precompress: true で gzip/brotli 事前圧縮(adapter-node / adapter-static)
  • 404/500 ページのカスタマイズ: +error.sveltefallback: '404.html'
  • HTTPS の強制: HSTS ヘッダー設定
  • ロールバック手順: 各プラットフォームの instant rollback 機能を把握

トラブルシューティング

症状原因解決
GitHub Pages で _app/ が 404.nojekyll が落ちているactions/upload-pages-artifactinclude-hidden-files: true
サブパス配下でリンクが切れるbase が反映されていないkit.paths.base に環境変数で渡す。リンクは [X](/foo) を rehype で書き換えるか、{base}/foo
Cloudflare で node:fs エラーnodejs_compat フラグ未設定wrangler.tomlcompatibility_flags = ["nodejs_compat"]
Vercel で「Function timeout」Hobby プランは 10 秒export const config = { maxDuration: 60 } または Pro プラン
ISR で更新が反映されないキャッシュが期限内bypassToken 付き URL で revalidate、または expiration 短縮
Docker でビルドが OOMnode:22-alpine のメモリ制限NODE_OPTIONS=--max-old-space-size=4096 または builder ステージのメモリ拡張

関連ページ

次のステップ

デプロイした後の運用フェーズに進みましょう。

  1. セキュリティ対策 — 本番公開前に CSP/CSRF を必ず設定
  2. モニタリング — エラートラッキングとパフォーマンス計測
  3. パフォーマンス最適化 — Core Web Vitals 改善ループ