この記事の対象読者
- Svelteを使い始めたが、周辺技術の選定に迷っている方
- React/Vue から Svelte への移行を検討している方
- SvelteKitでフルスタック開発を始めたい方
- Svelteでデスクトップアプリ開発に興味がある方
この記事で得られること
- バックエンド連携: Hono、SvelteKit API routes、Supabase の使い分け
- データベース: Drizzle ORM と Prisma の比較と選定基準
- UIコンポーネント: shadcn-svelte、Skeleton の実践的な導入方法
- デスクトップ開発: Tauri + Rust による軽量ネイティブアプリの構築
- エッジ対応: Cloudflare Workers + D1 での高速デプロイ
この記事で扱わないこと
- Svelte/SvelteKit 自体の基本文法(別記事参照)
- 各技術の網羅的なAPIリファレンス
- 本番環境でのセキュリティ設定の詳細
1. なぜ技術スタックの選定が重要なのか
「Svelte使い始めたけど、バックエンドどうしよう...」
正直に告白すると、私も最初はここで詰まりました。React時代は Next.js + Prisma + Vercel という「黄金パターン」があったのに、Svelteに来た途端、選択肢の海に投げ出された感覚。
でも1年ほど色々試した結果、Svelteには Svelte なりの「最強の組み合わせ」があることがわかってきました。
結論から言うと、Svelteの「コンパイラベース」という特性が、特定の技術スタックと驚くほど相性が良いんです。
この記事では、私が実際のプロジェクトで試行錯誤した結果たどり着いた「Svelteと相性の良い技術スタック」を、用途別に整理してお伝えします。
技術選定の背景が理解できたところで、具体的なスタック構成を見ていきましょう。
2. 前提知識の確認
本題に入る前に、この記事で登場する用語を整理しておきます。
2.1 SvelteKit とは
SvelteKit(スベルトキット)は、Svelte公式のフルスタックフレームワークです。Reactにおける Next.js、Vueにおける Nuxt.js に相当します。ファイルベースルーティング、SSR/SSG(Server-Side Rendering / Static Site Generation)、APIエンドポイントなどを提供します。
2.2 ORM(Object-Relational Mapping)とは
ORM は、データベースのテーブルをプログラミング言語のオブジェクトとして扱うための仕組みです。SQLを直接書く代わりに、TypeScriptのコードでデータベース操作ができます。
2.3 Edge Runtime とは
Edge Runtime は、ユーザーに近いサーバー(エッジサーバー)でコードを実行する仕組みです。Cloudflare Workers や Vercel Edge Functions がこれに該当します。従来のサーバーより低レイテンシーで応答できます。
2.4 Tauri とは
Tauri(タウリ)は、Rust製のデスクトップアプリケーションフレームワークです。Electronの代替として注目されており、バンドルサイズが圧倒的に小さいのが特徴です。
これらの用語を押さえたところで、具体的な技術スタックの解説に進みましょう。
3. 技術スタック全体像
まず、Svelteエコシステムの全体像を俯瞰してみましょう。
┌─────────────────────────────────────────────────────────────┐
│ フロントエンド │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Svelte 5 │ │ SvelteKit │ │ shadcn-svelte│ │
│ │ (コア) │ │(フレームワーク)│ │ (UI) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ バックエンド │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Hono │ │ Supabase │ │ SvelteKit │ │
│ │ (APIサーバー) │ │ (BaaS) │ │ (API routes)│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ データベース │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Drizzle ORM │ │ Prisma │ │ Cloudflare │ │
│ │(SQL-first) │ │(スキーマfirst)│ │ D1 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ デプロイ先 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Cloudflare │ │ Vercel │ │ Tauri │ │
│ │ Workers │ │ │ │ (デスクトップ)│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
全体像が把握できたところで、各レイヤーの技術を具体的に見ていきましょう。
4. バックエンド連携:3つの選択肢
4.1 SvelteKit API Routes(組み込み機能)
SvelteKitには標準でAPIエンドポイント機能が備わっています。シンプルなAPIなら追加ライブラリ不要です。
// src/routes/api/users/+server.ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const GET: RequestHandler = async () => {
const users = [
{ id: 1, name: '田中太郎', email: 'tanaka@example.com' },
{ id: 2, name: '佐藤花子', email: 'sato@example.com' }
];
return json(users);
};
export const POST: RequestHandler = async ({ request }) => {
const body = await request.json();
// バリデーションとDB保存処理
return json({ success: true, id: 3 }, { status: 201 });
};
メリット:
- 追加依存なし
- SvelteKitの型システムと完全統合
- load関数との連携がスムーズ
デメリット:
- 複雑なAPI設計には向かない
- 型安全なRPC(Remote Procedure Call)が標準では提供されない
4.2 Hono + SvelteKit(型安全なRPC)
Hono(ホノ)は、日本発の高速Webフレームワークです。Cloudflare Workers などのEdge環境で動作し、SvelteKitと組み合わせると型安全なAPIが構築できます。
// src/lib/server/api.ts
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
const app = new Hono()
.basePath('/api')
.get('/users', async (c) => {
const users = await db.select().from(usersTable);
return c.json(users);
})
.post(
'/users',
zValidator('json', z.object({
name: z.string().min(1),
email: z.string().email()
})),
async (c) => {
const data = c.req.valid('json');
const [user] = await db.insert(usersTable).values(data).returning();
return c.json(user, 201);
}
);
export type AppType = typeof app;
export default app;
// src/routes/api/[...paths]/+server.ts
import { Hono } from 'hono';
import app from '$lib/server/api';
import type { RequestHandler } from './$types';
const handler: RequestHandler = async ({ request }) => {
return app.fetch(request);
};
export const GET = handler;
export const POST = handler;
export const PUT = handler;
export const DELETE = handler;
// src/lib/client/api.ts(フロントエンドから呼び出し)
import { hc } from 'hono/client';
import type { AppType } from '$lib/server/api';
export const client = hc<AppType>('/');
// 使用例(完全に型安全!)
const response = await client.api.users.$get();
const users = await response.json();
// users の型が自動推論される
メリット:
- 型安全なRPC(フロントエンドとバックエンドで型を共有)
- Edge環境での高速動作
- Zodによるバリデーション統合
デメリット:
- 学習コストがやや高い
- SvelteKitのform actionsとの統合に工夫が必要
4.3 Supabase(BaaS:Backend as a Service)
Supabase(スーパーベース)は、オープンソースの Firebase 代替です。PostgreSQLベースで、認証・データベース・ストレージ・リアルタイム機能を提供します。
// src/lib/supabase.ts
import { createClient } from '@supabase/supabase-js';
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public';
export const supabase = createClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY);
// src/routes/+page.server.ts
import { supabase } from '$lib/supabase';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async () => {
const { data: users, error } = await supabase
.from('users')
.select('*')
.order('created_at', { ascending: false });
if (error) throw error;
return { users };
};
<!-- src/routes/+page.svelte -->
<script lang="ts">
let { data } = $props();
</script>
<h1>ユーザー一覧</h1>
<ul>
{#each data.users as user}
<li>{user.name} ({user.email})</li>
{/each}
</ul>
メリット:
- 環境構築が圧倒的に速い
- 認証・ストレージ・リアルタイムが統合済み
- 無料枠が充実
デメリット:
- 複雑なクエリには制限がある
- ベンダーロックインのリスク
バックエンド選定の判断基準
| ユースケース | 推奨技術 |
|---|---|
| シンプルなCRUD API | SvelteKit API Routes |
| 型安全なRPC + Edge対応 | Hono |
| 認証込みで素早く構築 | Supabase |
| マイクロサービス構成 | Hono(独立デプロイ) |
バックエンドの選択肢が理解できたところで、次はデータベース層を見ていきましょう。
5. データベース:Drizzle vs Prisma
5.1 Drizzle ORM(SQL-firstアプローチ)
Drizzle ORM(ドリズル)は、TypeScriptでスキーマを定義し、SQLライクな構文でクエリを書くORMです。
// src/lib/server/db/schema.ts
import { pgTable, serial, text, timestamp, boolean } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
isActive: boolean('is_active').default(true),
createdAt: timestamp('created_at').defaultNow()
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
content: text('content'),
authorId: serial('author_id').references(() => users.id),
createdAt: timestamp('created_at').defaultNow()
});
// src/lib/server/db/index.ts
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';
import { env } from '$env/dynamic/private';
const client = postgres(env.DATABASE_URL);
export const db = drizzle(client, { schema });
// クエリ例
import { db } from '$lib/server/db';
import { users, posts } from '$lib/server/db/schema';
import { eq } from 'drizzle-orm';
// 全ユーザー取得
const allUsers = await db.select().from(users);
// JOINクエリ
const usersWithPosts = await db
.select({
userName: users.name,
postTitle: posts.title
})
.from(users)
.leftJoin(posts, eq(users.id, posts.authorId));
// 挿入
const [newUser] = await db
.insert(users)
.values({ name: '山田太郎', email: 'yamada@example.com' })
.returning();
5.2 Prisma(スキーマファーストアプローチ)
Prisma(プリズマ)は、専用のスキーマ言語(PSL: Prisma Schema Language)でデータモデルを定義し、型安全なクライアントを自動生成するORMです。
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
isActive Boolean @default(true)
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
author User @relation(fields: [authorId], references: [id])
authorId Int
createdAt DateTime @default(now())
}
// クエリ例
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// リレーション込みで取得
const usersWithPosts = await prisma.user.findMany({
include: { posts: true }
});
// 条件付き取得
const activeUsers = await prisma.user.findMany({
where: { isActive: true },
orderBy: { createdAt: 'desc' }
});
Drizzle vs Prisma 比較表
| 項目 | Drizzle | Prisma |
|---|---|---|
| スキーマ定義 | TypeScript | 独自言語(PSL) |
| クエリ構文 | SQLライク | 独自API |
| バンドルサイズ | 約7.4KB | 約2MB(エンジン込み) |
| Edge対応 | ネイティブ対応 | Accelerate必要 |
| 学習コスト | SQL知識が活きる | PSL習得が必要 |
| マイグレーション | SQL生成 → 手動適用 | 自動適用 |
私の選定基準
Drizzleを選ぶ場合:
- Cloudflare Workers / Edge環境でデプロイしたい
- SQLの知識を活かしたい
- バンドルサイズを最小化したい
Prismaを選ぶ場合:
- チームにSQL苦手な人がいる
- リレーションを多用する複雑なデータモデル
- 自動マイグレーションで楽したい
データベース層が決まったところで、次はUIコンポーネントの選択を見ていきましょう。
6. UIコンポーネント:shadcn-svelte と仲間たち
6.1 shadcn-svelte(コピペ型UIライブラリ)
shadcn-svelte(シャドシーエヌ・スベルト)は、React版 shadcn/ui の非公式Svelte移植版です。コンポーネントをプロジェクトにコピーして使う「所有型」のアプローチが特徴です。
# セットアップ
npx shadcn-svelte@latest init
# コンポーネント追加
npx shadcn-svelte@latest add button
npx shadcn-svelte@latest add card
npx shadcn-svelte@latest add form
<!-- src/routes/+page.svelte -->
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import * as Card from '$lib/components/ui/card';
</script>
<Card.Root class="w-[350px]">
<Card.Header>
<Card.Title>アカウント作成</Card.Title>
<Card.Description>必要事項を入力してください</Card.Description>
</Card.Header>
<Card.Content>
<form>
<label class="block mb-4">
<span class="text-sm font-medium">メールアドレス</span>
<input
type="email"
class="mt-1 block w-full rounded-md border px-3 py-2"
placeholder="example@example.com"
/>
</label>
<Button type="submit" class="w-full">登録</Button>
</form>
</Card.Content>
</Card.Root>
6.2 Skeleton(テーマシステム付きUIキット)
Skeleton(スケルトン)は、Tailwind CSSベースのUIキットで、テーマシステムが充実しています。
# セットアップ
npm create skeleton-app@latest my-app
<script>
import { AppBar, Avatar, LightSwitch } from '@skeletonlabs/skeleton';
</script>
<AppBar>
<svelte:fragment slot="lead">
<Avatar src="/avatar.png" />
</svelte:fragment>
<svelte:fragment slot="trail">
<LightSwitch />
</svelte:fragment>
</AppBar>
UIライブラリ選定の判断基準
| ライブラリ | 特徴 | 向いているプロジェクト |
|---|---|---|
| shadcn-svelte | カスタマイズ性最強 | デザインにこだわりたいプロジェクト |
| Skeleton | テーマ切り替え簡単 | ダークモード必須のアプリ |
| Flowbite-Svelte | コンポーネント数が多い | 管理画面・ダッシュボード |
| Bits UI | ヘッドレス(スタイルなし) | 完全オリジナルデザイン |
UIの選択肢が理解できたところで、次はフォームバリデーションの実装を見ていきましょう。
7. フォームバリデーション:Superforms + Zod
SvelteKitでのフォーム処理には、Superforms(スーパーフォームズ)が定番です。
7.1 基本セットアップ
npm install sveltekit-superforms zod
// src/lib/schemas/user.ts
import { z } from 'zod';
export const userSchema = z.object({
name: z.string().min(1, '名前は必須です').max(50, '50文字以内で入力してください'),
email: z.string().email('有効なメールアドレスを入力してください'),
age: z.number().min(0, '0以上の数値を入力してください').max(150, '有効な年齢を入力してください')
});
export type UserSchema = typeof userSchema;
// src/routes/register/+page.server.ts
import { superValidate, fail } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import { userSchema } from '$lib/schemas/user';
import type { Actions, PageServerLoad } from './$types';
export const load: PageServerLoad = async () => {
const form = await superValidate(zod(userSchema));
return { form };
};
export const actions: Actions = {
default: async ({ request }) => {
const form = await superValidate(request, zod(userSchema));
if (!form.valid) {
return fail(400, { form });
}
// データベースに保存
console.log('登録データ:', form.data);
return { form, success: true };
}
};
<!-- src/routes/register/+page.svelte -->
<script lang="ts">
import { superForm } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import { userSchema } from '$lib/schemas/user';
let { data } = $props();
const { form, errors, enhance, submitting } = superForm(data.form, {
validators: zod(userSchema)
});
</script>
<form method="POST" use:enhance>
<div class="mb-4">
<label for="name" class="block text-sm font-medium">名前</label>
<input
id="name"
name="name"
bind:value={$form.name}
class="mt-1 block w-full rounded-md border px-3 py-2"
class:border-red-500={$errors.name}
/>
{#if $errors.name}
<p class="mt-1 text-sm text-red-500">{$errors.name}</p>
{/if}
</div>
<div class="mb-4">
<label for="email" class="block text-sm font-medium">メールアドレス</label>
<input
id="email"
name="email"
type="email"
bind:value={$form.email}
class="mt-1 block w-full rounded-md border px-3 py-2"
class:border-red-500={$errors.email}
/>
{#if $errors.email}
<p class="mt-1 text-sm text-red-500">{$errors.email}</p>
{/if}
</div>
<div class="mb-4">
<label for="age" class="block text-sm font-medium">年齢</label>
<input
id="age"
name="age"
type="number"
bind:value={$form.age}
class="mt-1 block w-full rounded-md border px-3 py-2"
class:border-red-500={$errors.age}
/>
{#if $errors.age}
<p class="mt-1 text-sm text-red-500">{$errors.age}</p>
{/if}
</div>
<button
type="submit"
disabled={$submitting}
class="w-full rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:opacity-50"
>
{$submitting ? '送信中...' : '登録'}
</button>
</form>
フォーム処理の基本が理解できたところで、デスクトップアプリ開発という別の世界を覗いてみましょう。
8. デスクトップアプリ:Tauri + Rust
8.1 なぜ Tauri + Svelte なのか
Tauri(タウリ)は、Electron の代替として注目されているデスクトップアプリフレームワークです。
| 比較項目 | Electron | Tauri |
|---|---|---|
| バンドルサイズ | 約150MB〜 | 約2-3MB |
| メモリ使用量 | 約300MB〜 | 約50MB〜 |
| バックエンド言語 | JavaScript (Node.js) | Rust |
| レンダリング | Chromium同梱 | OS標準WebView |
Svelte の「コンパイル後のコードが小さい」という特性と、Tauri の「バンドルサイズが小さい」という特性は、相性抜群です。
8.2 セットアップ
# 新規プロジェクト作成
npm create tauri-app@latest my-desktop-app -- --template sveltekit-ts
# 既存SvelteKitプロジェクトにTauriを追加
npm install -D @tauri-apps/cli
npx tauri init
// svelte.config.js(静的アダプターに変更)
import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
export default {
preprocess: vitePreprocess(),
kit: {
adapter: adapter({
fallback: 'index.html'
})
}
};
// src/routes/+layout.ts(SSR無効化)
export const prerender = true;
export const ssr = false;
8.3 Rust との連携(Tauri Commands)
// src-tauri/src/main.rs
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use tauri::command;
#[command]
fn greet(name: &str) -> String {
format!("Hello, {}! This message comes from Rust!", name)
}
#[command]
fn calculate_fibonacci(n: u32) -> u64 {
if n <= 1 {
return n as u64;
}
let mut a = 0u64;
let mut b = 1u64;
for _ in 2..=n {
let temp = a + b;
a = b;
b = temp;
}
b
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet, calculate_fibonacci])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
<!-- src/routes/+page.svelte -->
<script lang="ts">
import { invoke } from '@tauri-apps/api/core';
let name = $state('World');
let greeting = $state('');
let fibN = $state(10);
let fibResult = $state<number | null>(null);
async function greet() {
greeting = await invoke('greet', { name });
}
async function calculateFib() {
fibResult = await invoke('calculate_fibonacci', { n: fibN });
}
</script>
<div class="p-8">
<h1 class="text-2xl font-bold mb-4">Tauri + Svelte デモ</h1>
<div class="mb-6">
<h2 class="text-lg font-semibold mb-2">挨拶(Rustから)</h2>
<input
bind:value={name}
class="border rounded px-3 py-2 mr-2"
placeholder="名前を入力"
/>
<button
onclick={greet}
class="bg-blue-600 text-white px-4 py-2 rounded"
>
挨拶する
</button>
{#if greeting}
<p class="mt-2 text-green-600">{greeting}</p>
{/if}
</div>
<div>
<h2 class="text-lg font-semibold mb-2">フィボナッチ計算(Rust高速計算)</h2>
<input
type="number"
bind:value={fibN}
class="border rounded px-3 py-2 mr-2 w-24"
/>
<button
onclick={calculateFib}
class="bg-green-600 text-white px-4 py-2 rounded"
>
計算
</button>
{#if fibResult !== null}
<p class="mt-2">F({fibN}) = {fibResult}</p>
{/if}
</div>
</div>
デスクトップアプリの構築方法が理解できたところで、最後にエッジ環境へのデプロイを見ていきましょう。
9. エッジデプロイ:Cloudflare Workers + D1
9.1 なぜ Cloudflare なのか
Cloudflare Workers は、世界中のエッジサーバーでコードを実行できるサーバーレスプラットフォームです。SvelteKitとの相性が特に良く、公式アダプターが用意されています。
# Cloudflareアダプターのインストール
npm install -D @sveltejs/adapter-cloudflare
// svelte.config.js
import adapter from '@sveltejs/adapter-cloudflare';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
export default {
preprocess: vitePreprocess(),
kit: {
adapter: adapter()
}
};
9.2 D1(エッジSQLiteデータベース)との連携
// wrangler.jsonc
{
"name": "my-sveltekit-app",
"main": ".svelte-kit/cloudflare/_worker.js",
"compatibility_date": "2025-01-01",
"assets": {
"binding": "ASSETS",
"directory": ".svelte-kit/cloudflare"
},
"d1_databases": [
{
"binding": "DB",
"database_name": "my-database",
"database_id": "your-database-id"
}
]
}
// src/app.d.ts
declare global {
namespace App {
interface Platform {
env: {
DB: D1Database;
};
}
}
}
export {};
// src/lib/server/db.ts
import { drizzle } from 'drizzle-orm/d1';
import * as schema from './schema';
export function getDb(d1: D1Database) {
return drizzle(d1, { schema });
}
// src/routes/api/users/+server.ts
import { json } from '@sveltejs/kit';
import { getDb } from '$lib/server/db';
import { users } from '$lib/server/schema';
import type { RequestHandler } from './$types';
export const GET: RequestHandler = async ({ platform }) => {
const db = getDb(platform!.env.DB);
const allUsers = await db.select().from(users);
return json(allUsers);
};
10. ユースケース別ガイド
ユースケース1: 個人ブログ / ポートフォリオ(最小構成)
想定読者: 初めてSvelteKitでサイトを作る個人開発者
推奨スタック:
- フロントエンド: SvelteKit(静的生成)
- スタイリング: Tailwind CSS
- デプロイ: Cloudflare Pages(無料)
サンプルコード:
// svelte.config.js
import adapter from '@sveltejs/adapter-static';
export default {
kit: {
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: undefined,
precompress: true
})
}
};
<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
let { data } = $props();
</script>
<svelte:head>
<title>{data.post.title} | My Blog</title>
<meta name="description" content={data.post.excerpt} />
</svelte:head>
<article class="prose mx-auto max-w-3xl px-4 py-8">
<h1>{data.post.title}</h1>
<time datetime={data.post.date}>{data.post.formattedDate}</time>
{@html data.post.content}
</article>
ユースケース2: SaaS MVP(フルスタック)
想定読者: スタートアップでMVPを素早く構築したいチーム
推奨スタック:
- フロントエンド: SvelteKit + shadcn-svelte
- バックエンド: Supabase(認証・DB・ストレージ)
- フォーム: Superforms + Zod
- デプロイ: Vercel
サンプルコード:
// src/hooks.server.ts
import { createServerClient } from '@supabase/ssr';
import { type Handle, redirect } from '@sveltejs/kit';
import { sequence } from '@sveltejs/kit/hooks';
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public';
const supabase: Handle = async ({ event, resolve }) => {
event.locals.supabase = createServerClient(
PUBLIC_SUPABASE_URL,
PUBLIC_SUPABASE_ANON_KEY,
{
cookies: {
getAll: () => event.cookies.getAll(),
setAll: (cookiesToSet) => {
cookiesToSet.forEach(({ name, value, options }) =>
event.cookies.set(name, value, { ...options, path: '/' })
);
}
}
}
);
event.locals.safeGetSession = async () => {
const { data: { session } } = await event.locals.supabase.auth.getSession();
if (!session) return { session: null, user: null };
const { data: { user }, error } = await event.locals.supabase.auth.getUser();
if (error) return { session: null, user: null };
return { session, user };
};
return resolve(event);
};
const authGuard: Handle = async ({ event, resolve }) => {
const { session, user } = await event.locals.safeGetSession();
event.locals.session = session;
event.locals.user = user;
if (!session && event.url.pathname.startsWith('/dashboard')) {
redirect(303, '/login');
}
return resolve(event);
};
export const handle = sequence(supabase, authGuard);
ユースケース3: 社内ツール / 管理画面
想定読者: 企業の内部ツールを効率よく作りたいエンジニア
推奨スタック:
- フロントエンド: SvelteKit + Skeleton
- バックエンド: Hono(型安全RPC)
- データベース: Drizzle + PostgreSQL
- デプロイ: Docker / 自社サーバー
サンプルコード:
// src/lib/server/api/index.ts
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';
import { usersRouter } from './routers/users';
import { productsRouter } from './routers/products';
import { reportsRouter } from './routers/reports';
const app = new Hono()
.use('*', logger())
.use('*', cors())
.basePath('/api')
.route('/users', usersRouter)
.route('/products', productsRouter)
.route('/reports', reportsRouter);
export type AppType = typeof app;
export default app;
// src/lib/server/api/routers/users.ts
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import { db } from '$lib/server/db';
import { users } from '$lib/server/db/schema';
import { eq } from 'drizzle-orm';
const createUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
role: z.enum(['admin', 'user', 'viewer'])
});
export const usersRouter = new Hono()
.get('/', async (c) => {
const allUsers = await db.select().from(users);
return c.json(allUsers);
})
.get('/:id', async (c) => {
const id = Number(c.req.param('id'));
const [user] = await db.select().from(users).where(eq(users.id, id));
if (!user) return c.json({ error: 'User not found' }, 404);
return c.json(user);
})
.post('/', zValidator('json', createUserSchema), async (c) => {
const data = c.req.valid('json');
const [newUser] = await db.insert(users).values(data).returning();
return c.json(newUser, 201);
})
.delete('/:id', async (c) => {
const id = Number(c.req.param('id'));
await db.delete(users).where(eq(users.id, id));
return c.json({ success: true });
});
ユースケース別の構成が理解できたところで、学習の次のステップを確認しましょう。
11. 学習ロードマップ
初級者向け(まずはここから)
-
SvelteKit公式チュートリアルを完走する
- https://learn.svelte.dev/
- 所要時間: 2-3時間
-
Tailwind CSSの基本を押さえる
- shadcn-svelteを使う前提知識として必須
-
簡単なCRUDアプリを作ってみる
- SvelteKit API Routes + SQLite(ローカル)で十分
中級者向け(実践に進む)
-
Supabase + SvelteKitで認証付きアプリを構築
-
Drizzle ORMを導入してみる
-
Superformsでフォームバリデーションを実装
上級者向け(さらに深く)
-
Hono + SvelteKitで型安全APIを構築
- RPC機能を使った完全型安全な通信を体験
-
Cloudflare Workers + D1でエッジデプロイ
-
Tauriでデスクトップアプリ化
12. まとめ
この記事では、Svelteと相性の良い技術スタックを以下の観点から解説しました:
- バックエンド: SvelteKit API Routes / Hono / Supabase の使い分け
- データベース: Drizzle(エッジ向け軽量)vs Prisma(機能豊富)
- UI: shadcn-svelte(カスタマイズ重視)vs Skeleton(テーマ重視)
- フォーム: Superforms + Zod による型安全なバリデーション
- デスクトップ: Tauri + Rust による軽量ネイティブアプリ
- エッジ: Cloudflare Workers + D1 による高速デプロイ
私の所感
正直に言うと、「万能の組み合わせ」は存在しません。
でも、Svelteの「コンパイラベース」という特性を活かすなら、軽量であることを意識した技術選定が吉だと感じています。
具体的には:
- Prisma より Drizzle(バンドルサイズ)
- Electron より Tauri(メモリ・サイズ)
- 自前サーバーより エッジ(レイテンシ)
もちろん、チームの習熟度やプロジェクトの要件によって最適解は変わります。
この記事が、あなたの技術選定の一助になれば幸いです。