「Next.jsは重厚すぎる」「もっとシンプルにフロントエンド+バックエンドを書きたい」。そう思ったことはありませんか?
SvelteKitは、React/Next.jsの複雑さに疲れた個人開発者の間で急速に支持を広げているWebアプリケーションフレームワークです。2024年にリリースされたSvelte 5では「runes」と呼ばれるリアクティビティシステムが導入され、より直感的でパフォーマンスの高いコードが書けるようになりました。
この記事では、2026年最新のSvelteKitの全てを、実際のコードを交えて徹底解説します。React/Next.jsからの移行を考えている方、これからWebアプリ開発を始める方、どちらにも役立つ内容です。
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
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のフォームアクションは、同じページ内でフォーム送信〜バックエンド処理までを完結できる強力な機能です。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を使った手動デプロイも可能です。
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 VPSやXServer 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でプロジェクトを作成し、小さなアプリから始めてみてください。驚くほど少ないコードで、サクサク動くアプリが完成するはずです。