この記事は ウェブクルー Advent Calendar 2025 の5日目の記事です。
昨日は @wchikarusatoさんの「 Github ProjectsのRoadmapでタスク管理してる話 」でした。
5日目は @shoshi-maruyama が担当いたします。よろしくお願いします。
SvelteKitを個人で触ってみたので、感想を共有して布教してみる。
株式会社ウェブクルー Advent Calendar 2025の5日目の記事を担当するフクロウ(@shoshi-maruyama)と申します。
普段はバックエンド開発でに携わっておりますが、個人でsveltekitを触っていて、Next.jsよりわかりやすく好きになったので、共有しようと思います。
- SvelteとSvelteKitの違い
- SvelteKitの主な特徴とメリット(特にNext.jsとの比較を交えながら)
を書いてみます。
Next.jsを使っている方も、新しい選択肢としてSvelteKitを知るのはアリかなと思うので、sveltekitの布教になると嬉しいです。(svelteの回し者ではないです。)
1. SvelteとSvelteKitとは?
関係性について、簡単にいうとvueでいうNuxt、reactでいうNextの関係です。わかりやすいですね。
Svelteとは?
Svelteは「コンパイラ」であり、UIを構築するためのコンポーネントフレームワークです。
ただ調べたところ、以下は、違いはありました。
-
仮想DOMがない: Reactが実行時に仮想DOMを比較してDOMを更新するのに対し、Svelteはビルド時にJavaScriptコードにコンパイルされ、直接DOMを操作します。
→大規模になったとしても、設計をちゃんとすれば、パフォーマンスは落ちにくい気がします。 -
軽量・高速: 実行時のオーバーヘッドが少なく、バンドルサイズも小さいため、設計次第ではreactよりも優れたパフォーマンスを発揮すると思います。
→仮想DOMがないので、ブラウザのメモリ消費少ないからだと理解してます。
あとは、** JavaScript、HTML、CSSの標準的な知識があれば、以下のような感じで独立して、書くことができるので、個人的には気に入ってます。
<script lang="ts">
const a = 1
</script>
<div class="card">
<p class=text>{a}</p>
</div>
<style>
.text {
color:red;
}
</style>
ちなみに、reactで書くと...
// MyComponent.tsx
import React from 'react';
// グローバルCSSをインポート(通常はルートコンポーネントかレイアウトで一度だけインポート)
// import './styles.css';
const MyComponent: React.FC = () => {
const a = 1; // JavaScriptのロジック
return (
// JSX (HTMLのような構文)
<div className="card"> {/* グローバルなクラス名を直接指定 */}
<p className="text">{a}</p>
</div>
);
};
export default MyComponent;
svelteの方がシンプルでそれぞれその要素が独立してるからわかりやすく感じます。
React.FCとかの決まり文言を書く必要もないのが理由です。
-
シンプルな記法:
$:を使ったリアクティブ宣言や、{#if}、{#each}といったHTMLライクなテンプレート構文は直感的で学習コストが低いのが特徴です。ReactのHooksやJSXの複雑さに比べ、より少ないコードで同じ機能を実現できることが多いです。
<!-- src/routes/minimal-example/+page.svelte -->
<script lang="ts">
let count = 0; // リアクティブな変数
let items = ['A', 'B', 'C']; // リストデータ
// countが変更されるたびに実行されるリアクティブ宣言
$: status = count % 2 === 0 ? '偶数' : '奇数';
function increment() {
count++;
}
</script>
<div class="card">
<h1>Count: {count} ({status})</h1>
<button on:click={increment}>+1</button>
{#if count >= 3}
<p>3以上です!</p>
{/if}
<ul>
{#each items as item}
<li>{item}</li>
{/each}
</ul>
</div>
<style>
.card { border: 1px solid #ccc; padding: 15px; text-align: center; }
h1 { color: #ff3e00; }
button { background-color: #007bff; color: white; border: none; padding: 8px 12px; cursor: pointer; }
ul { list-style: none; padding: 0; }
li { margin: 5px 0; }
</style>
reactはこうかな?
// MyComponent.tsx
import React, { useState, useMemo } from 'react'; // useStateとuseMemoをインポート
import styles from './MyComponent.module.css'; // CSS Modulesをインポート
const MyComponent: React.FC = () => {
// 1. リアクティブな変数 (useStateフックを使用)
const [count, setCount] = useState(0);
const [items] = useState(['A', 'B', 'C']); // リストデータは変更しないのでsetItemsは不要
// 2. countが変更されるたびに実行されるリアクティブ宣言 (useMemoフックを使用)
// Svelteの `$: status = ...` に相当
const status = useMemo(() => {
return count % 2 === 0 ? '偶数' : '奇数';
}, [count]); // countが依存関係
// 3. イベントハンドラ
const increment = () => {
setCount(prevCount => prevCount + 1); // setCount関数を使って状態を更新
};
return (
// JSX (JavaScript XML) 構文
<div className={styles.card}>
<h1 className={styles.title}>Count: {count} ({status})</h1>
<button onClick={increment} className={styles.button}>+1</button>
{/* 4. 条件分岐: 三項演算子または短絡評価 (Svelteの {#if} に相当) */}
{count >= 3 && (
<p>3以上です!</p>
)}
{/* 5. リストレンダリング: mapメソッド (Svelteの {#each} に相当) */}
<ul className={styles.list}>
{items.map((item, index) => (
<li key={index} className={styles.listItem}>{item}</li> // keyプロップは必須
))}
</ul>
</div>
);
};
export default MyComponent;
うーん。実際に比較してみると、慣れてるからreactでも分かるなあ...けど、初心者からするとsvelteの方が記法がシンプルに見える気がします。
svelteのplay groudもあるので、試してみてもらえると実感しやすいと思います。
https://svelte.dev/playground/hello-world?version=5.45.5
SvelteKitとは?
SvelteKitは、Svelteをベースにした「フルスタックなWebアプリケーションフレームワーク」です。
Svelteだけでは提供されない、以下のような機能を提供します。
- ファイルシステムベースのルーティング: ディレクトリ構造がそのままURLパスになる
- サーバーサイドレンダリング (SSR) / 静的サイト生成 (SSG) / SPA: ユースケースに応じたレンダリングモードを搭載
- APIエンドポイント (Server Routes): バックエンド処理をSvelteKit内で完結
- フォームアクション (Form Actions): サーバーサイドでのフォーム処理を簡潔に記述
- アダプターによる柔軟なデプロイ: Vercel, Netlify, Cloudflare Pages, Node.jsサーバーなど、様々な環境にしてます。
これらは、Next.jsに似たような機能がありますね。
2. SvelteKitとNext.jsを比較してみる
SvelteKitが多くの開発者から注目されている理由を、特によく使われるNext.jsとの比較書いてみます。
① パフォーマンスと軽量性
- Svelteのコンパイラアプローチ: Next.jsがReactの仮想DOMを使用するのに対し、SvelteKitはSvelteのコンパイラによって、ビルド時に最適化されたバニラJavaScriptを生成します。これにより、実行時のランタイムが非常に小さく、アプリケーションの起動が早い印象
- バンドルサイズの小ささ: 生成されるコードが軽量であるため、バンドルサイズが小さく、ネットワーク負荷が軽減されます。これは、特にモバイル環境や低帯域幅のユーザーにとっても嬉しいし、私たち開発者にとっても嬉しい。
② 開発体験(DX)
SvelteKitは開発者にとっても非常に優しい設計です。
-
高速なHMR (Hot Module Replacement): Viteをベースにしているため、開発中のコード変更が即座にブラウザに反映され、ストレスなく開発を進められます。
→いわゆるホットリロードしてくれるということですね。 - TypeScriptのサポート: デフォルトでTypeScriptをサポートしており、型安全な開発が可能です。
③ フルスタックな機能とユニークな強み
SvelteKitは単一のフレームワークでフロントエンドからバックエンドまでをカバーしてくれます。
-
ファイルシステムベースルーティング:
src/routesディレクトリ配下のファイル・フォルダがそのままルーティングになります。これはNext.jsと非常によく似た仕組みです。 - サーバーサイドレンダリング (SSR) / 静的サイト生成 (SSG) / SPA: プロジェクトの要件に合わせて最適なレンダリングモードを選択できます。
-
APIエンドポイント (Server Routes):
+server.tsファイルを作成するだけで、GETやPOSTなどのHTTPメソッドに対応したAPIエンドポイントを簡単に構築できます。Next.jsのAPI Routesに相当します。 - フォームアクション (Form Actions): これはSvelteKitの特にユニークで強力な機能です。サーバーサイドでフォームデータを処理するための機能で、Next.jsのServer Actionsに似ていますが、SvelteKitではより簡潔かつプログレッシブエンハンスメントに対応したフォームを実装できます。クライアント側のJavaScriptをほとんど書くことなく、安全なフォームを構築できるのが魅力です。
④ 柔軟なデプロイ
SvelteKitは「アダプター」という仕組みを通じて、様々な環境にデプロイできます。
-
@sveltejs/adapter-vercel: Vercelにデプロイ (Next.jsと同様に最適化されています) -
@sveltejs/adapter-netlify: Netlifyにデプロイ -
@sveltejs/adapter-node: Node.jsサーバーとしてデプロイ -
@sveltejs/adapter-static: 静的サイトとしてデプロイ (Cloudflare Pages, GitHub Pagesなど)
※②、③、④はNext.jsにもあります。
Next.jsとSvelteKitのファイル命名規則:決め打ちの範囲と粒度の違い
ファイル名の「決め打ち」ルールもNext.jsと同じ印象です。
| 機能 / フレームワーク | Next.js (App Router) | SvelteKit |
|---|---|---|
| メインUI (ページ) | page.tsx |
+page.svelte |
| レイアウトUI | layout.tsx |
+layout.svelte |
| エラーUI | error.tsx |
+error.svelte |
| APIエンドポイント | route.ts |
+server.ts |
| ページ用データ取得 |
page.tsx 内で async コンポーネントとして記述 or Server Actions
|
+page.server.ts (サーバーサイド) または +page.ts (ユニバーサル) |
| レイアウト用データ取得 |
layout.tsx 内で async コンポーネントとして記述 |
+layout.server.ts (サーバーサイド) または +layout.ts (ユニバーサル) |
| フォームアクション |
page.tsx 内で Server Actions として記述 ('use server') |
+page.server.ts 内で actions オブジェクトとして定義 |
| ローディングUI | loading.tsx |
(+page.svelte 内で {#await} ブロックなど) |
共通点としては、特定のURLパスに対応するファイル名が「決め打ち」なところです。ディレクトリ構造がそのままURLになる、これは、Next.jsを使っている方はなるほどと感じてもらえるかと思います。
Next.js (App Router): page.tsx (ページ), layout.tsx (レイアウト)
SvelteKit: +page.svelte (ページ), +layout.svelte (レイアウト)
APIエンドポイントも、Next.jsは route.ts、SvelteKitは +server.ts と決まってます。
相違点:SvelteKitは機能ごとに専用ファイル
SvelteKitは、ページやレイアウトに付随する「データ取得」「フォームアクション」「エラー処理」といった機能ごとに、専用の「決め打ちファイル」があるのがNext.jsと違う点です。
Next.js (App Router)だと、こういうロジックの多くは page.tsx や layout.tsx の中で async コンポーネントとして書いたかな?
一方SvelteKitでは、一つのURLセグメントに対して、複数の「+」プレフィックス付きファイルが共存して、それぞれが特定の役割を担ってるのが特徴です。個人的には仕様として分離できるので、嬉しい仕様です。
ちょっと文章ではわかりづらい気がしたんで、ここもNext.jsとSvelteKitのサンプルを書いてみました。
src/app/users/
└── page.tsx <-- ここにUI、データ取得、フォームアクションが統合される
// src/app/users/page.tsx
// フォームアクションをこのファイル内で定義(または別の 'use server' ファイルからimport)
'use server';
async function addUser(formData: FormData) {
const name = formData.get('name');
// ユーザー追加のDB処理など
console.log(`ユーザー ${name} を追加`);
// 処理後、リダイレクトやrevalidatePathなど
}
export default async function UsersPage() {
// サーバーサイドでのデータ取得(Server Componentとして)
const users = await fetch('https://api.example.com/users').then(res => res.json());
return (
<div>
<h1>ユーザー一覧</h1>
<ul>
{users.map((user: any) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
<h2>新しいユーザーを追加</h2>
{/* フォームアクションを直接 <form action={addUser}> に渡す */}
<form action={addUser}>
<input type="text" name="name" placeholder="ユーザー名" required />
<button type="submit">追加</button>
</form>
</div>
);
}
src/routes/users/
├── +page.svelte <-- ページのUIだけを記述
└── +page.server.ts <-- ページのサーバーサイドデータ取得とフォームアクションを記述
<!-- src/routes/users/+page.svelte -->
<script lang="ts">
// +page.server.ts から渡されたデータをここで受け取る
export let data; // data.users と data.form が入ってくる
</script>
<div>
<h1>ユーザー一覧</h1>
<ul>
{#each data.users as user}
<li>{user.name}</li>
{/each}
</ul>
<h2>新しいユーザーを追加</h2>
<!-- フォームはmethod="POST"で送信。処理は+page.server.tsのactionsが担う -->
<form method="POST">
<input type="text" name="name" placeholder="ユーザー名" required />
<button type="submit">追加</button>
</form>
{#if data.form?.success}
<p style="color: green;">ユーザー追加成功!</p>
{/if}
{#if data.form?.error}
<p style="color: red;">エラー: {data.form.error}</p>
{/if}
</div>
// src/routes/users/+page.server.ts
import type { PageServerLoad, Actions } from './$types';
import { fail } from '@sveltejs/kit';
// ページのサーバーサイドデータ取得ロジック(load関数)
export const load: PageServerLoad = async () => {
const res = await fetch('https://api.example.com/users');
const users = await res.json();
return { users }; // +page.svelte に { users: [...] } として渡される
};
// フォームアクションの定義(actionsオブジェクト)
export const actions: Actions = {
default: async ({ request }) => { // デフォルトのフォーム送信アクション
const data = await request.formData();
const name = data.get('name');
if (!name || typeof name !== 'string') {
return fail(400, { error: 'ユーザー名が必要です' });
}
try {
// ユーザー追加のDB処理など
console.log(`ユーザー ${name} を追加`);
// 成功したら +page.svelte に { success: true } を返す
return { success: true };
} catch (error) {
return fail(500, { error: 'ユーザー追加に失敗しました' });
}
}
};
個人的には、この仕様が気に入ってます。
+page.server.tsがBFFの役割も担いつつ、ページ表示前の処理がファイル単位になっていることで明確に分離され、ブラウザ側に通信内容が出たりしないので便利だなって思いました。
おわりに
書いてみると、sveltekitの魅力より、svelteの魅力紹介になった気がするなあ(笑)
自分の振り返りとしても書いてみましたが、基本的な記法理解できたが、SvelteやSvelteKitの深い知識がまだ無いなって感じがしたので、これからもドキュメント読んで深く理解してスマートに開発できるようにして、sveltekitを布教したいです。
ちなみに、google trendsで調べてみてもそこまで浸透してなくて悲しくなりました...
やっぱり、Next.jsの人気が強い...
明日は@reon_kunishi_wcさんの投稿になります。ご期待ください!
