0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Svelteと相性抜群の技術スタック2025〜フロントエンドからデスクトップアプリまで完全網羅〜

Posted at

この記事の対象読者

  • 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. 学習ロードマップ

初級者向け(まずはここから)

  1. SvelteKit公式チュートリアルを完走する

  2. Tailwind CSSの基本を押さえる

    • shadcn-svelteを使う前提知識として必須
  3. 簡単なCRUDアプリを作ってみる

    • SvelteKit API Routes + SQLite(ローカル)で十分

中級者向け(実践に進む)

  1. Supabase + SvelteKitで認証付きアプリを構築

  2. Drizzle ORMを導入してみる

  3. Superformsでフォームバリデーションを実装

上級者向け(さらに深く)

  1. Hono + SvelteKitで型安全APIを構築

    • RPC機能を使った完全型安全な通信を体験
  2. Cloudflare Workers + D1でエッジデプロイ

  3. Tauriでデスクトップアプリ化


12. まとめ

この記事では、Svelteと相性の良い技術スタックを以下の観点から解説しました:

  1. バックエンド: SvelteKit API Routes / Hono / Supabase の使い分け
  2. データベース: Drizzle(エッジ向け軽量)vs Prisma(機能豊富)
  3. UI: shadcn-svelte(カスタマイズ重視)vs Skeleton(テーマ重視)
  4. フォーム: Superforms + Zod による型安全なバリデーション
  5. デスクトップ: Tauri + Rust による軽量ネイティブアプリ
  6. エッジ: Cloudflare Workers + D1 による高速デプロイ

私の所感

正直に言うと、「万能の組み合わせ」は存在しません。

でも、Svelteの「コンパイラベース」という特性を活かすなら、軽量であることを意識した技術選定が吉だと感じています。

具体的には:

  • Prisma より Drizzle(バンドルサイズ)
  • Electron より Tauri(メモリ・サイズ)
  • 自前サーバーより エッジ(レイテンシ)

もちろん、チームの習熟度やプロジェクトの要件によって最適解は変わります。
この記事が、あなたの技術選定の一助になれば幸いです。


参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?