【Svelte】SvelteKitとは?フルスタック開発の基礎から実践まで徹底解説
Svelteは、コンパイラベースのアプローチを採用したモダンなフロントエンドフレームワークです。この記事では、Svelteの特徴とSvelteKitを使ったフルスタック開発について解説します。
Svelteの主な特徴
コンパイラベースのアプローチ
ReactやVueがランタイムで仮想DOMを使用するのに対し、Svelteはビルド時にコードをコンパイルして最適化されたバニラJavaScriptを生成します。
これにより以下のメリットがあります。
- バンドルサイズが小さい
- ランタイムオーバーヘッドがない
- パフォーマンスが高い
シンプルなリアクティビティ
変数への代入だけでリアクティブな更新が発生します。
<script>
let count = 0;
function increment() {
count += 1; // これだけでUIが更新される
}
</script>
<button on:click={increment}>
クリック数: {count}
</button>
少ないボイラープレート
useState、useEffectなどのフックが不要.svelteファイル内にHTML、CSS、JSをまとめて記述- CSSはデフォルトでスコープ化される
組み込み機能が豊富
- トランジション/アニメーション
- ストア(状態管理)
- スロット
- コンテキストAPI
React/Vueとの比較
| 項目 | Svelte | React | Vue |
|---|---|---|---|
| 仮想DOM | なし | あり | あり |
| バンドルサイズ | 小さい | 大きめ | 中程度 |
| 学習コスト | 低い | 中程度 | 中程度 |
| エコシステム | 成長中 | 非常に大きい | 大きい |
SvelteKitとは
SvelteKitは、Svelte公式のフルスタックフレームワークです。ReactにおけるNext.js、VueにおけるNuxtに相当します。
Svelte = UIライブラリ(コンポーネント作成)
SvelteKit = フレームワーク(ルーティング、SSR、API等を含む)
SvelteKitの主な機能
| 機能 | 説明 |
|---|---|
| ファイルベースルーティング | src/routes/以下の構造がURLになる |
| SSR/SSG/CSR | ページごとにレンダリング方式を選択可能 |
| APIエンドポイント | +server.tsでバックエンドAPIを定義 |
| データローディング | +page.server.tsでサーバーサイドのデータ取得 |
| フォームアクション | プログレッシブエンハンスメント対応のフォーム処理 |
ファイル構造の例
src/routes/
├── +page.svelte # / のページ
├── +layout.svelte # 共通レイアウト
├── about/
│ └── +page.svelte # /about のページ
├── blog/
│ ├── +page.svelte # /blog のページ
│ ├── +page.server.ts # サーバーサイドデータ取得
│ └── [slug]/
│ └── +page.svelte # /blog/:slug の動的ルート
└── api/
└── users/
└── +server.ts # /api/users のAPIエンドポイント
SvelteKitのコード例
ページ(+page.svelte)
<script>
export let data; // +page.server.tsから受け取る
</script>
<h1>{data.title}</h1>
<ul>
{#each data.posts as post}
<li>{post.title}</li>
{/each}
</ul>
データ取得(+page.server.ts)
export async function load() {
const posts = await db.getPosts();
return { title: 'ブログ', posts };
}
APIエンドポイント(+server.ts)
import { json } from '@sveltejs/kit';
export async function GET() {
const users = await db.getUsers();
return json(users);
}
export async function POST({ request }) {
const data = await request.json();
const user = await db.createUser(data);
return json(user, { status: 201 });
}
フォルダ構成のベストプラクティス
小規模プロジェクト(公式推奨ベース)
src/
├── lib/
│ ├── components/ # UIコンポーネント
│ │ ├── Button.svelte
│ │ └── Header.svelte
│ ├── server/ # サーバー専用コード
│ │ └── db.ts
│ └── utils.ts # 共有ユーティリティ
├── routes/
│ ├── +layout.svelte # 共通レイアウト
│ ├── +page.svelte # トップページ
│ ├── about/
│ │ └── +page.svelte
│ └── api/
│ └── users/
│ └── +server.ts
├── app.html
└── app.css
中〜大規模プロジェクト
src/
├── lib/
│ ├── components/
│ │ ├── ui/ # 汎用UIコンポーネント
│ │ │ ├── Button.svelte
│ │ │ └── Input.svelte
│ │ └── features/ # 機能別コンポーネント
│ │ ├── auth/
│ │ │ └── LoginForm.svelte
│ │ └── users/
│ │ └── UserCard.svelte
│ ├── server/
│ │ ├── db/
│ │ │ ├── index.ts # DB接続
│ │ │ └── schema.ts # Drizzle/Prismaスキーマ
│ │ └── services/ # ビジネスロジック
│ │ └── userService.ts
│ ├── stores/ # Svelteストア(状態管理)
│ │ └── auth.ts
│ ├── types/ # 型定義
│ │ └── index.ts
│ └── utils/
│ ├── format.ts
│ └── validation.ts
├── routes/
│ ├── (app)/ # 認証必要なルート(グループ)
│ │ ├── +layout.svelte
│ │ ├── dashboard/
│ │ └── settings/
│ ├── (auth)/ # 認証系ルート(グループ)
│ │ ├── login/
│ │ └── register/
│ ├── api/
│ │ └── v1/
│ │ └── users/
│ └── +layout.svelte
├── hooks.server.ts # サーバーサイドhooks
├── hooks.client.ts # クライアントサイドhooks
├── app.d.ts # 型定義
├── app.html
└── app.css
重要なポイント
| ディレクトリ | 用途 |
|---|---|
$lib | src/libのエイリアス、インポートが楽になる |
$lib/server | サーバー専用コード(クライアントにバンドルされない) |
(group) | URLに影響しないルートグループ化 |
[param] | 動的ルート |
hooks(ミドルウェア)
SvelteKitにはhooksがあり、ミドルウェア的な役割を果たします。
hooks.server.ts(最も使う)
すべてのサーバーリクエストを処理する前に実行されます。
// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
// リクエスト前の処理
const session = await getSession(event.cookies);
event.locals.user = session?.user;
// 認証チェック
if (event.url.pathname.startsWith('/dashboard') && !event.locals.user) {
return new Response('Redirect', {
status: 303,
headers: { Location: '/login' }
});
}
// 次の処理へ
const response = await resolve(event);
// レスポンス後の処理(ヘッダー追加など)
response.headers.set('X-Custom-Header', 'value');
return response;
};
複数のhookを組み合わせる
import { sequence } from '@sveltejs/kit/hooks';
const auth: Handle = async ({ event, resolve }) => {
// 認証処理
return resolve(event);
};
const logger: Handle = async ({ event, resolve }) => {
console.log(`${event.request.method} ${event.url.pathname}`);
return resolve(event);
};
// 順番に実行
export const handle = sequence(logger, auth);
event.localsの型定義
// src/app.d.ts
declare global {
namespace App {
interface Locals {
user: User | null;
}
}
}
export {};
型安全なAPI通信
SvelteKitでも型安全なAPI通信を実現できます。
SvelteKitのネイティブ機能
+page.server.tsのload関数は自動的に型推論されます。
// +page.server.ts
export async function load() {
const users = await db.getUsers();
return { users }; // 型が自動推論される
}
<!-- +page.svelte -->
<script lang="ts">
export let data; // { users: User[] } と型推論される
</script>
tRPCを使用する場合
npm install @trpc/server @trpc/client trpc-sveltekit
// lib/trpc/router.ts
import { t } from './trpc';
export const router = t.router({
getUsers: t.procedure.query(async () => {
return await db.getUsers();
}),
createUser: t.procedure
.input(z.object({ name: z.string() }))
.mutation(async ({ input }) => {
return await db.createUser(input);
}),
});
export type AppRouter = typeof router;
<!-- +page.svelte -->
<script lang="ts">
import { trpc } from '$lib/trpc/client';
// 完全に型安全
const users = trpc.getUsers.query();
</script>
Hono RPC(軽量な代替)
// バックエンド
import { Hono } from 'hono';
const app = new Hono()
.get('/users', async (c) => {
const users = await db.getUsers();
return c.json(users);
})
.post('/users', async (c) => {
const body = await c.req.json();
return c.json(await db.createUser(body));
});
export type AppType = typeof app;
// フロントエンド
import { hc } from 'hono/client';
import type { AppType } from './server';
const client = hc<AppType>('/api');
const res = await client.users.$get(); // 型安全
比較
| 方法 | 型安全 | 追加依存 | 複雑さ |
|---|---|---|---|
| SvelteKit load関数 | ○ | なし | 低 |
| tRPC | ◎ | あり | 中 |
| Hono RPC | ○ | あり | 低 |
シンプルに始めたい場合はSvelteKitのload関数で十分です。API設計を厳密にしたい場合はtRPC、軽量でシンプルな場合はHono RPCがおすすめです。
モノレポでの開発
SvelteKitはフルスタックフレームワークなので、FEとBEを同じプロジェクト内で完結できます。
一体型(小〜中規模向け)
my-app/
├── src/
│ ├── routes/ # ページ(FE)
│ │ ├── +page.svelte
│ │ └── api/ # APIエンドポイント(BE)
│ │ └── users/+server.ts
│ └── lib/ # 共有コード
├── package.json
└── svelte.config.js
分離型モノレポ(大規模向け)
monorepo/
├── apps/
│ ├── web/ # Svelte/React等(FE)
│ └── api/ # Express/Hono等(BE)
├── packages/
│ └── shared/ # 共有型・ユーティリティ
├── package.json
└── pnpm-workspace.yaml # or turbo.json
適したユースケース
Svelteは以下の場面で特に力を発揮します。
- パフォーマンスが重要なアプリケーション
- 小〜中規模のプロジェクト
- 素早いプロトタイピング
- バンドルサイズを抑えたい場合
参考文献
まとめ
Svelteはコンパイラベースのアプローチにより、小さいバンドルサイズと高いパフォーマンスを実現するフレームワークです。SvelteKitを使えば、SSR/SSG、APIエンドポイント、型安全なデータ取得などフルスタック開発に必要な機能が揃っています。
学習コストも低く、シンプルなコードで開発できるため、新規プロジェクトの選択肢として検討する価値があります。