スポンサーリンク
スポンサーリンク
1️⃣ はじめに:なぜSvelteKitが来るのか
JavaScriptフレームワーク戦国時代、ReactやVueの次に脚光を浴びるのがSvelteKitです。従来のSSRとSPAは相反するアーキテクチャとされていましたが、SvelteKitはその両立を軽量かつ高速に実現します。
- バンドル前コンパイル:余計なランタイムが不要
- ファイルベースルーティング:設定不要で簡潔
- load関数:組み込みでSSRデータフェッチ
国内情報はまだ少なく、アーリーアダプター向けの先行者利益を狙える技術です。
2️⃣ 開発環境の準備
必要なもの
- Node.js 16以上
- Git
- 任意のコードエディタ (VSCode推奨)
SvelteKit プロジェクト作成
npm init svelte@next my-app
cd my-app
npm install
npm install -D @types/node
npm run dev -- --open
次のようなディレクトリが自動生成されます:
my-app/
├ src/
│ ├ lib/
│ ├ routes/
│ ├ app.html
├ static/
└ svelte.config.js
3️⃣ Hello World:最速スターティング
src/routes/+page.svelte
<script lang="ts">
let name = '世界';
</script>
<h1>こんにちは、{name}!</h1>
<input bind:value={name} placeholder="名前を入力" />
解説:
<script lang="ts">
で TypeScript対応bind:value
による双方向バインディング- ファイル名
+page.svelte
がルート/
にマッピング
ブラウザでフォームに文字を打つと、即時にUIが更新されることを確認しましょう。
4️⃣ ファイルベースルーティング
ネストされたルート
src/routes/
├ +page.svelte --> /
└ about/
└ +page.svelte --> /about
アクセスすると、それぞれ違うコンポーネントが表示されます。
動的ルート
src/routes/blog/[slug]/+page.svelte
<script lang="ts">
export let params: { slug: string };
</script>
<h1>記事: {params.slug}</h1>
このように []
で囲むと動的URLを簡単実装できます。
5️⃣ SSR入門:load
関数
サーバー側でデータを取得し、初期HTML に埋め込む方法です。
src/routes/users/+page.ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ fetch }) => {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
const users = await res.json();
return { users };
};
src/routes/users/+page.svelte
<script lang="ts">
export let data: { users: { id: number; name: string }[] };
</script>
<ul>
{#each data.users as user}
<li>{user.name}</li>
{/each}
</ul>
解説:
load
でフェッチ → SSR- ブラウザに届くHTMLに既にユーザー一覧がレンダリングされる
- SEO・初期表示速度 激的に向上
6️⃣ SPA化:クライアントサイドナビゲーション
ページ間移動が フルページリロードなし に。
標準リンク
<script>
import { goto } from '$app/navigation';
</script>
<button on:click={() => goto('/about')}>Aboutへ</button>
または
<a href="/about" sveltekit:prefetch>Aboutへ</a>
解説:
goto
はプログラム的に遷移sveltekit:prefetch
でホバー時に事前取得- ページ遷移が即時かつスムーズ
7️⃣ APIフェッチの使い分け
- SSR (
load
関数) → SEO重視・初期表示 - CSR (
onMount
) → インタラクティブ要素の更新
CSR例
<script lang="ts">
import { onMount } from 'svelte';
let post;
onMount(async () => {
const res = await fetch('/api/random');
post = await res.json();
});
</script>
{#if post}
<article>{post.title}</article>
{:else}
<p>読み込み中…</p>
{/if}
8️⃣ デプロイ:Vercel / Netlify
Vercel
npm i -g vercel
vercel login
vercel --prod
Netlify
GitHub連携で Automatic Deploy を設定。
9️⃣ 応用編:認証付きSSR/DB接続
+page.server.ts
import { redirect } from '@sveltejs/kit';
export const load = async ({ locals }) => {
if (!locals.user) throw redirect(302, '/login');
const todos = await db.getTodos(locals.user.id);
return { todos };
};
解説:
locals
で セッション情報 を SSR 時に受け取る- 未ログイン ユーザーをクライアント前に リダイレクト
🔚 まとめ & 次のステップ
- SvelteKit は SSR×SPA を両立 する次世代フレームワーク
- load, goto, prefetch など独自 API が強力
- デプロイも数コマンドで完了 ⇒ 開発〜運用 の効率化
次は Edge Runtime や Prerendering、GraphQL 接続などを解説予定です!
おまけ:リアルタイム Todo アプリ構築
SvelteKit の SSR × SPA 機能 を活かして、リアルタイム同期付きの Todo アプリを作成します。
- SSR で初期データを配信
- SPA でクライアント側操作を即時反映
- WebSocket (Socket.IO) で多人数同時編集対応
🏗️ 構成概要
層 | 技術 |
---|---|
フロントエンド | SvelteKit (+ TypeScript) |
API 層 | SvelteKit Endpoints (src/routes/api) |
リアルタイム同期 | Socket.IO (server + client) |
データベース | SQLite + Prisma ORM |
データベース設定 (Prisma)
npm install prisma @prisma/client
npx prisma init --datasource-provider sqlite
prisma/schema.prisma
:
datasource db { provider = "sqlite" url = "file:./dev.db" }
generator client { provider = "prisma-client-js" }
model Todo {
id Int @id @default(autoincrement())
text String
done Boolean @default(false)
createdAt DateTime @default(now())
}
npx prisma migrate dev --name init
API エンドポイント (CRUD)
src/routes/api/todos/+server.ts
import { json } from '@sveltejs/kit';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export async function GET() {
const todos = await prisma.todo.findMany({ orderBy: { createdAt: 'desc' } });
return json({ todos });
}
export async function POST({ request }) {
const { text } = await request.json();
const todo = await prisma.todo.create({ data: { text } });
return json({ todo });
}
export async function PUT({ request }) {
const { id, done } = await request.json();
const todo = await prisma.todo.update({ where: { id }, data: { done } });
return json({ todo });
}
export async function DELETE({ request }) {
const { id } = await request.json();
await prisma.todo.delete({ where: { id } });
return json({ success: true });
}
解説:
+server.ts
ファイルベースで REST API を実装GET
で初回 SSR 配信用データ取得POST
/PUT
/DELETE
でデータ操作後クライアントに JSON を返却
Socket.IO サーバー統合
src/hooks.server.ts
import { sequence } from '@sveltejs/kit/hooks';
import { Server } from 'socket.io';
import type { Handle } from '@sveltejs/kit';
let io: Server;
export const handle: Handle = sequence(async ({ event, resolve }) => {
if (!io) {
const server = await event.platform?.node?.httpServer; // Vercel 以外
io = new Server(server);
io.on('connection', socket => {
socket.on('todo:update', data => {
socket.broadcast.emit('todo:update', data);
});
});
}
return resolve(event);
});
解説:
hooks.server.ts
で初回リクエスト時に Socket.IO サーバーを起動todo:update
イベントをクライアント間でブロードキャスト
フロントエンド実装
src/routes/+page.svelte
<script lang="ts">
import { onMount } from 'svelte';
import io from 'socket.io-client';
let todos = [];
let text = '';
const socket = io();
async function fetchTodos() {
const res = await fetch('/api/todos');
todos = (await res.json()).todos;
}
async function addTodo() {
const res = await fetch('/api/todos', { method:'POST', body:JSON.stringify({ text }) });
const { todo } = await res.json();
todos = [todo, ...todos];
socket.emit('todo:update', { action: 'add', todo });
text = '';
}
function toggleDone(id) {
const todo = todos.find(t => t.id === id);
fetch('/api/todos', { method:'PUT', body:JSON.stringify({ id, done: !todo.done }) });
todos = todos.map(t => t.id === id ? { ...t, done: !t.done } : t);
socket.emit('todo:update', { action: 'toggle', id });
}
onMount(() => {
fetchTodos();
socket.on('todo:update', data => {
if (data.action === 'add') todos = [data.todo, ...todos];
if (data.action === 'toggle') todos = todos.map(t => t.id === data.id ? { ...t, done: !t.done } : t);
});
});
</script>
<input bind:value={text} placeholder="新しいTODOを入力" />
<button on:click={addTodo}>追加</button>
<ul>
{#each todos as { id, text, done }}
<li on:click={() => toggleDone(id)} class:done={done}>{text}</li>
{/each}
</ul>
<style>
.done { text-decoration: line-through; color: #888; }
</style>
解説:
- SSR で取得した
todos
をonMount
で初期化し、画面に描画 - CRUD API コール後に Socket.IO で他クライアントへ変更通知
- リアクティブ変数
todos
の更新で即時 UI 反映