「Next.jsは重厚すぎる」「もっとシンプルにフロントエンド+バックエンドを書きたい」。そう思ったことはありませんか?

SvelteKitは、React/Next.jsの複雑さに疲れた個人開発者の間で急速に支持を広げているWebアプリケーションフレームワークです。2024年にリリースされたSvelte 5では「runes」と呼ばれるリアクティビティシステムが導入され、より直感的でパフォーマンスの高いコードが書けるようになりました。

この記事では、2026年最新のSvelteKitの全てを、実際のコードを交えて徹底解説します。React/Next.jsからの移行を考えている方、これからWebアプリ開発を始める方、どちらにも役立つ内容です。

XServer VPS

広告

XServer VPS

開発環境構築に最適なVPS。月額1,046円〜

最大1,046円相当

詳しく見る →

SvelteKitとは? なぜ今選ぶべきなのか

SvelteKitは、Svelte(スヴェルト)というコンパイラベースのUIフレームワークをベースにした、フルスタックのWebアプリケーションフレームワークです。Next.jsがReactベースであるのと同様に、SvelteKitはSvelteベースで構築されています。

Svelte最大の特徴は「コンパイラアプローチ」です。Reactのようにブラウザ上で仮想DOMを動かすのではなく、ビルド時に効率的なJavaScriptにコンパイルされます。結果、バンドルサイズが小さく、動作が爆速になります。

項目 SvelteKit Next.js (React)
バンドルサイズ(Hello World) 〜15KB 〜70KB
リアクティビティ コンパイル時(runes) 仮想DOM + Hooks
学習曲線 低い(直感的な構文) 中程度(Hooks/状態管理)
ファイルベースルーティング
SSR / SSG / SPA
サーバーサイドロジック統合 ✅(+server.ts / フォームアクション) ✅(Route Handlers / Actions)

特に個人開発者にとって嬉しいのは、「書いたコード量と実際の動作速度がほぼ比例する」という点です。Svelteは「書かないことが最高のパフォーマンス最適化」という哲学を持っており、無駄なボイラープレートが極限まで削ぎ落とされています。

SvelteKitプロジェクトのセットアップ

まずはプロジェクトを作成しましょう。Node.js 20以降がインストールされていることを確認してください。

npm create svelte@latest my-app
cd my-app
npm install
npm run dev

プロンプトでは以下のように選択するのがおすすめです:

  • SvelteKit demo app → スケルトンプロジェクト(またはデモアプリ)
  • TypeScript → Yes(2026年はTypeScriptが標準)
  • ESLint / Prettier → Yes(コード品質維持に)
  • Playwright / Vitest → 必要に応じてYes

生成されるプロジェクト構成は次のようになります:

my-app/
├── src/
│   ├── routes/       ← ページとAPIのルーティング
│   ├── lib/          ← 共有コンポーネント・ユーティリティ
│   ├── app.html      ← ルートHTMLテンプレート
│   └── app.css       ← グローバルCSS
├── static/           ← 静的ファイル(favicon, 画像など)
├── svelte.config.js  ← SvelteKit設定 + アダプター設定
├── vite.config.ts    ← Vite設定
└── package.json
ConoHa VPS

広告

ConoHa VPS

VPS・インフラ構築に最適。月額880円〜

最大880円相当

詳しく見る →

Svelte 5の核心:Runes完全理解

Svelte 5最大の変更点はrunes(ルーン)と呼ばれる新しいリアクティビティシグナルシステムです。従来のlet count = 0による暗黙的なリアクティビティから、明示的なシグナルベースのリアクティビティに移行しました。

$state — 状態の宣言

<script lang="ts">
  let count = $state(0);
  let name = $state('SvelteKit');
  let items = $state<string[]>([]);

  function increment() {
    count += 1;
  }
</script>

<button onclick={increment}>
  クリック数: {count}
</button>

$state()で宣言された変数はリアクティブになり、値が変更されると自動的にUIが更新されます。ReactのuseStateと違い、setCount()のようなセッター関数は不要で、普通の変数として代入できます。

$derived — 派生状態

既存の状態から計算される値は$derivedを使います。

<script lang="ts">
  let price = $state(1000);
  let taxRate = $state(0.1);
  let total = $derived(price * (1 + taxRate));
  let formatted = $derived(`¥${total.toLocaleString()}`);
</script>

<p>税込価格: {formatted}</p>

$derivedは依存する値が変わったときだけ再計算されるため、パフォーマンスに優れています。ReactのuseMemoと似ていますが、はるかにシンプルです。

$effect — 副作用

<script lang="ts">
  let count = $state(0);

  $effect(() => {
    // countが変わるたびに実行される
    console.log(`カウントが${count}に更新されました`);
    document.title = `カウント: ${count}`;
  });

  // クリーンアップ
  $effect(() => {
    const interval = setInterval(() => {
      console.log('tick');
    }, 1000);
    return () => clearInterval(interval); // コンポーネント破棄時に実行
  });
</script>

$effectはReactのuseEffectに対応しますが、依存配列を手動で指定する必要はありません。Svelteが自動的に依存関係を追跡します。

$props — コンポーネントのプロパティ

<!-- Button.svelte -->
<script lang="ts">
  let { label = 'クリック', disabled = false, onclick } = $props();
</script>

<button {disabled} {onclick}>
  {label}
</button>

親コンポーネントから受け取る値は$props()で宣言します。デフォルト値もここで指定可能です。

ファイルベースルーティング完全マスター

SvelteKitのルーティングはsrc/routes/ディレクトリのファイル構造に基づきます。Next.jsのApp Routerと非常に似ていますが、いくつか独自のルールがあります。

基本的なルート

src/routes/
├── +page.svelte       ← /(トップページ)
├── about/
│   └── +page.svelte   ← /about
├── blog/
│   ├── +page.svelte   ← /blog(記事一覧)
│   └── [slug]/
│       └── +page.svelte ← /blog/hello-world(動的ルート)
└── api/
    └── posts/
        └── +server.ts ← /api/posts(APIエンドポイント)

+page.svelte — ページコンポーネント

<!-- src/routes/+page.svelte -->
<script lang="ts">
  let { data } = $props();
</script>

<h1>{data.title}</h1>
<p>{data.description}</p>

+layout.svelte — レイアウト

<!-- src/routes/+layout.svelte -->
<script lang="ts">
  let { children } = $props();
</script>

<nav>
  <a href="/">ホーム</a>
  <a href="/blog">ブログ</a>
</nav>

{@render children()}

{@render children()}はSvelte 5の新しいスロット構文で、子コンポーネント(該当ルートのページ)がここにレンダリングされます。

動的ルートとパラメーター

<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
  let { data } = $props();
</script>

<article>
  <h1>{data.post.title}</h1>
  <div>{@html data.post.content}</div>
</article>

データフェッチング — load関数の極意

SvelteKitのデータフェッチはload関数が中心です。+page.server.ts(サーバー専用)または+page.ts(ユニバーサル)にexportすることで、ページにデータを渡せます。

サーバーサイドのload(+page.server.ts)

// src/routes/blog/[slug]/+page.server.ts
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ params, fetch, locals }) => {
  const slug = params.slug;

  // Supabase / Drizzle / Prisma などでDBから取得
  const post = await getPostBySlug(slug);

  if (!post) {
    throw error(404, '記事が見つかりません');
  }

  return {
    post
  };
};

サーバーサイドのloadではデータベースに直接アクセスできるため、APIキーやパスワードが漏れる心配がありません。DBクエリもここで行います。

クライアントサイドのload(+page.ts)

// src/routes/+page.ts
import type { Load } from './$types';

export const load: Load = async ({ fetch }) => {
  const res = await fetch('https://api.example.com/posts');
  const posts = await res.json();

  return { posts };
};

ユニバーサルloadはサーバーでもクライアントでも実行されます。外部APIを叩く場合や、クライアントサイドでも使いたいデータはこちらが便利です。

🚀 SvelteKitの開発環境をVPSで始めるなら

XServer VPSをチェック →

広告

フォームアクション — バックエンド処理を超シンプルに

SvelteKitのフォームアクションは、同じページ内でフォーム送信〜バックエンド処理までを完結できる強力な機能です。APIエンドポイントを別途作る必要がありません。

// src/routes/contact/+page.server.ts
import type { Actions } from './$types';

export const actions: Actions = {
  default: async ({ request }) => {
    const data = await request.formData();
    const name = data.get('name') as string;
    const email = data.get('email') as string;
    const message = data.get('message') as string;

    // バリデーション
    const errors: Record<string, string> = {};
    if (!name) errors.name = '名前は必須です';
    if (!email) errors.email = 'メールアドレスは必須です';
    if (Object.keys(errors).length) {
      return { success: false, errors };
    }

    // メール送信 or DB保存
    await sendContactEmail({ name, email, message });

    return { success: true, message: '送信しました!' };
  }
};
<!-- src/routes/contact/+page.svelte -->
<script lang="ts">
  let { form } = $props();
</script>

<form method="POST">
  <input name="name" placeholder="お名前" />
  {#if form?.errors?.name}
    <p class="error">{form.errors.name}</p>
  {/if}

  <input name="email" placeholder="メールアドレス" />
  {#if form?.errors?.email}
    <p class="error">{form.errors.email}</p>
  {/if}

  <textarea name="message" placeholder="お問い合わせ内容"></textarea>
  <button type="submit">送信する</button>
</form>

{#if form?.success}
  <p>{form.message}</p>
{/if}

JavaScriptが無効でも動作する(プログレッシブエンハンスメント)のが最大の強み。use:enhanceを使えば、JS有効時はよりスムーズなUXを提供できます。

認証実装ハンズオン

個人開発のWebアプリで避けて通れないのが認証。SvelteKitでは、Cookieベースのセッション管理とSupabase Auth/Lucia Authを使った実装が一般的です。

Auth.js(NextAuth v5)のSvelteKit対応

2026年現在、Auth.js(旧NextAuth)がSvelteKitを公式サポートしています。OAuthプロバイダー(Google, GitHub, Discordなど)を使った認証を数行で実装できます。

// src/auth.ts
import { SvelteKitAuth } from '@auth/sveltekit';
import GitHub from '@auth/core/providers/github';
import Google from '@auth/core/providers/google';
import { GITHUB_ID, GITHUB_SECRET, GOOGLE_ID, GOOGLE_SECRET, AUTH_SECRET } from '$env/static/private';

export const { handle, signIn, signOut } = SvelteKitAuth({
  providers: [
    GitHub({ clientId: GITHUB_ID, clientSecret: GITHUB_SECRET }),
    Google({ clientId: GOOGLE_ID, clientSecret: GOOGLE_SECRET })
  ],
  secret: AUTH_SECRET,
  trustHost: true
});
// src/hooks.server.ts
import { handle as authHandle } from './auth';

export const handle = authHandle;

これだけでGitHub/Googleログインが使えるようになります。各ページでセッション情報を取得するには:

// src/routes/+page.server.ts
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async (event) => {
  const session = await event.locals.auth();
  return { session };
};

Supabase Authとの連携

Supabaseを使った認証も人気です。SupabaseはBaaSとして無料枠が充実しており、認証・DB・ストレージをまとめて使えるため、個人開発者の強い味方です。

// src/hooks.server.ts
import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit';

export const handle = async ({ event, resolve }) => {
  event.locals.supabase = createSupabaseServerClient({
    supabaseUrl: SUPABASE_URL,
    supabaseKey: SUPABASE_ANON_KEY,
    event
  });

  event.locals.getSession = async () => {
    const { data: { session } } = await event.locals.supabase.auth.getSession();
    return session;
  };

  return resolve(event);
};

データベース連携(Drizzle ORM + SQLite)

SvelteKitでSQLiteを扱うなら、Drizzle ORMとの組み合わせが最も生産性が高いです。軽量で型安全、しかもSvelteKitのサーバーロード関数と相性抜群です。

// src/lib/server/db/schema.ts
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';

export const posts = sqliteTable('posts', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  title: text('title').notNull(),
  slug: text('slug').notNull().unique(),
  content: text('content').notNull(),
  createdAt: text('created_at').notNull().$defaultFn(() => new Date().toISOString())
});

export const users = sqliteTable('users', {
  id: text('id').primaryKey(),
  email: text('email').notNull().unique(),
  name: text('name'),
  createdAt: text('created_at').notNull().$defaultFn(() => new Date().toISOString())
});
// src/lib/server/db/index.ts
import { drizzle } from 'drizzle-orm/libsql';
import { createClient } from '@libsql/client';

const client = createClient({
  url: DATABASE_URL,          // libSQL: turso://... or file:./data.db
  authToken: DATABASE_AUTH_TOKEN // Tursoの場合のみ
});

export const db = drizzle(client);

実際のページでデータを取得する例:

// src/routes/blog/+page.server.ts
import { db } from '$lib/server/db';
import { posts } from '$lib/server/db/schema';
import { desc } from 'drizzle-orm';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async () => {
  const allPosts = await db.select()
    .from(posts)
    .orderBy(desc(posts.createdAt))
    .limit(20);

  return { posts: allPosts };
};

アダプターとデプロイ

SvelteKitの最大の利点の一つが、多様なデプロイ先に対応していることです。アダプターを切り替えるだけで、Cloudflare Pages、Vercel、Netlify、Node.jsサーバー、Denoなど、様々な環境にデプロイできます。

Cloudflare Pagesにデプロイ(おすすめ)

個人開発者にはCloudflare Pages + SvelteKitの組み合わせが最もおすすめです。無料枠が generous で、CDN経由で世界中に爆速配信されます。

// svelte.config.js
import adapter from '@sveltejs/adapter-cloudflare';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

/** @type {import('@sveltejs/kit').Config} */
const config = {
  preprocess: vitePreprocess(),
  kit: {
    adapter: adapter({
      routes: {
        include: ['/*'],
        exclude: ['']
      }
    })
  }
};

export default config;
npm install --save-dev @sveltejs/adapter-cloudflare

Cloudflare PagesはGit連携でpushするだけで自動デプロイが行われます。wrangler pages deployを使った手動デプロイも可能です。

☁️ Cloudflare Pagesにデプロイするための開発環境は

XServer VPSで整える →

広告

Vercelにデプロイ

npm install --save-dev @sveltejs/adapter-vercel
// svelte.config.js
import adapter from '@sveltejs/adapter-vercel';

export default {
  kit: { adapter: adapter({ isr: { expiration: 60 } }) }
};

VercelではISR(Incremental Static Regeneration)もサポートされており、動的コンテンツを高速配信できます。

Node.jsサーバーにデプロイ

VPSをお持ちなら、Node.jsアダプターで自分でサーバーをホストすることもできます。

npm install --save-dev @sveltejs/adapter-node
// svelte.config.js
import adapter from '@sveltejs/adapter-node';

export default {
  kit: {
    adapter: adapter({ out: 'build' })
  }
};
npm run build
node build/index.js  # ポート3000で起動

これをVPS上でsystemdやDockerで管理すれば、本番稼働させられます。特にConoHa VPSXServer VPSの安価なプランでも十分に動作します。

SvelteKit + Tailwind CSS v4でスタイリング

SvelteKitはTailwind CSSとの相性が非常に良いです。2026年現在、Tailwind CSS v4が標準です。

npm install tailwindcss @tailwindcss/vite
// vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import tailwindcss from '@tailwindcss/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [tailwindcss(), sveltekit()]
});
/* src/app.css */
@import "tailwindcss";
// src/routes/+layout.svelte
<script>
  import '../app.css';
  let { children } = $props();
</script>

{@render children()}

これだけで全ページでTailwind CSSが使えるようになります。Svelteのクラスベースのテンプレート構文とTailwindのユーティリティファーストは非常に相性が良いです。

パフォーマンス最適化10の実践テクニック

SvelteKitはデフォルトでも高速ですが、さらにパフォーマンスを引き出すテクニックを紹介します。

1. プリレンダリング(SSG)を活用する

// src/routes/+page.ts または個別のルートで
export const prerender = true;

ブログ記事やドキュメントなど、動的でないページはプリレンダリングして静的なHTMLとして出力しましょう。S3やCDNで配信でき、サーバーコストがゼロになります。

2. ページ単位でレンダリングモードを指定する

// 静的生成(SSG)
export const prerender = true;

// サーバーサイドレンダリング(SSR: デフォルト)
export const ssr = true;

// クライアントサイドレンダリング(CSR: SPAモード)
export const ssr = false;

ルートごとにモードを切り替えられるので、ログインが必要なページはSSR、公開ブログはSSG、管理画面はCSR、といった使い分けが可能です。

3. $stateのローカル化

// ❌ 避ける:コンポーネント外のモジュールレベルのstate
export let globalCount = $state(0);

// ✅ 推奨:コンポーネント内に閉じる
let localCount = $state(0);

4. 画像最適化

// Viteのビルドイン画像最適化を活用
import Logo from '$lib/assets/logo.webp';

// SvelteKitの画像コンポーネント(サードパーティ)
// unplugin-icons でアイコンをツリーシェイキング

5. 遅延ローディング

// 条件付きimportでコード分割
{#if showChart}
  {#await import('$lib/chart/Chart.svelte') then { default: Chart }}
    <Chart data={data} />
  {/await}
{/if}

6. フォームのプログレッシブエンハンスメント

<form method="POST" use:enhance>
  <!-- JS有効時はAjax送信、無効時は通常フォーム送信 -->
</form>

これだけで、JavaScriptが有効なときはスムーズに、無効なときは従来のフォームとして動作します。

7. Cache-Controlヘッダーの設定

// src/hooks.server.ts
export const handle = async ({ event, resolve }) => {
  const response = await resolve(event);

  // 静的ページはキャッシュ
  if (event.url.pathname.startsWith('/blog/')) {
    response.headers.set('Cache-Control', 'public, max-age=3600, s-maxage=3600');
  }

  return response;
};

実践:簡易ブログアプリの構築

ここまでの知識を組み合わせて、簡易ブログアプリを構築してみましょう。

プロジェクト構成

src/
├── routes/
│   ├── +page.svelte           ← トップ(記事一覧)
│   ├── +page.server.ts        ← トップのload関数
│   ├── +layout.svelte         ← 全体レイアウト
│   ├── blog/
│   │   └── [slug]/
│   │       ├── +page.svelte   ← 個別記事ページ
│   │       └── +page.server.ts ← 個別記事のload関数
│   └── admin/
│       ├── +page.svelte       ← 管理画面TOP
│       └── new/
│           ├── +page.svelte   ← 新規記事作成
│           └── +page.server.ts ← フォームアクション
├── lib/
│   ├── components/
│   │   ├── Header.svelte
│   │   └── Footer.svelte
│   └── server/
│       └── db/
│           ├── index.ts
│           └── schema.ts
└── app.css

ブログ一覧ページ

<!-- src/routes/+page.svelte -->
<script lang="ts">
  let { data } = $props();
  let { posts } = data;
</script>

<h1>ブログ</h1>

{#each posts as post}
  <article>
    <h2><a href="/blog/{post.slug}">{post.title}</a></h2>
    <p class="date">{new Date(post.createdAt).toLocaleDateString('ja-JP')}</p>
  </article>
{/each}

SvelteKit + Supabase + StripeでミニSaaS

SvelteKitは個人開発のミニSaaSにも最適です。SupabaseをDB・認証に、Stripeで課金を組み合わせれば、モダンなSaaSが驚くほど短期間で作れます。

実際に筆者はSvelteKit + Supabase + Stripeで、月額課金のSaaSを約2週間でリリースしました。Next.jsと比較してコード量は体感で6〜7割程度。特にフォームアクションとload関数の統合により、APIエンドポイントを別途書く必要がないのが大きいです。

TypeScriptとの統合

SvelteKitはTypeScriptを完全サポートしており、$typesからの自動型生成が非常に強力です。

// +page.server.ts から自動生成される型
import type { PageServerLoad, Action } from './$types';

// URLパラメーターの型も自動生成
// params: { slug: string } が自動推論

load関数の戻り値の型も自動的にページコンポーネントに伝搬されるため、dataの型が完全に推論されます。これにより、手動で型定義を書く必要が大幅に減ります。

よくある落とし穴と解決策

「$stateが更新されない」

よくあるのが、配列やオブジェクトのミューテーションでUIが更新されない問題です。

// ❌ 配列のミューテーションは検知されない
items.push(newItem);

// ✅ 新しい配列を代入する
items = [...items, newItem];

// または
items = [...items, newItem];

// Svelte 5では配列のpush/popは$stateでラップしていれば一部OK
// ただし安全に新しい配列を作る方が確実

「load関数が2回実行される」

これは仕様です。SvelteKitのload関数はサーバーとクライアントの両方で実行されることがあります(ユニバーサルloadの場合)。サーバー専用なら+page.server.tsを使いましょう。

「アダプターによって動かない」

Node.jsのネイティブモジュールやfsを使うコードは、Cloudflare Pagesでは動作しません。デプロイ先に応じたアダプターを選び、それに合わせたコードを書く必要があります。

まとめ:SvelteKitは個人開発者の最強の武器になる

SvelteKitは、「少ないコードで、高速に動くWebアプリを作りたい」という個人開発者の理想を体現するフレームワークです。

主な魅力を改めてまとめると:

  • コンパイラベースでバンドルサイズが超軽量(15KB〜)
  • runes($state/$derived/$effect)で直感的なリアクティビティ
  • load関数 + フォームアクションでAPI不要のフルスタック開発
  • アダプター切り替えでCloudflare Pages / Vercel / Netlify に柔軟にデプロイ
  • TypeScriptとの統合が強力で、型安全な開発がほぼ自動
  • 学習曲線が緩やかで、React/Next.jsから移行しても違和感が少ない

Next.jsが「大規模プロダクション向け」だとすれば、SvelteKitは「個人開発者向けのベストチョイス」と言えるでしょう。特に個人開発のミニSaaSやツール系Webアプリでは、SvelteKitの生産性の高さが真価を発揮します。

まずはnpm create svelte@latestでプロジェクトを作成し、小さなアプリから始めてみてください。驚くほど少ないコードで、サクサク動くアプリが完成するはずです。