こんにちは!「SvelteKitで作るフルスタック個人ブログ」シリーズの第2回へようこそ。第1回では、SvelteKitの概要を紹介し、Skeleton UIとTailwind CSSを使ってブログの基本レイアウトを構築しました。今回は、ブログのUIをさらに充実させ、記事一覧や記事詳細ページを作ります。最初は静的データ(モックデータ)を使い、Svelteの直感的な記述を活かしてコンポーネントを構築します。ReactやNext.jsとの比較も交えながら、SvelteKitの魅力を深掘りしましょう!
この記事の目標
- SvelteKitのファイルベースのルーティングを理解する。
- Svelteコンポーネントで記事一覧と記事詳細ページを構築する。
- Skeleton UIを活用して美しいUIを作る。
- Svelteのリアクティブ変数を使ってデータを簡単に管理する。
- Reactとの違いを比較して、Svelteの開発体験(DX)の良さを体感する。
最終的には、ブログのフロントエンドが完成し、静的な記事データを表示できる状態を目指します。では、始めましょう!
SvelteKitのルーティングをおさらい
SvelteKitはファイルベースのルーティングを採用しており、プロジェクトのsrc/routes/
フォルダ内のファイル構造がそのままURLに対応します。例えば:
-
src/routes/+page.svelte
→ ホームページ (/
) -
src/routes/blog/+page.svelte
→ ブログ一覧ページ (/blog
) -
src/routes/about/+page.svelte
→ Aboutページ (/about
)
今回は、以下のページを追加します:
-
ブログ一覧ページ (
/blog
):記事のリストを表示。 -
記事詳細ページ (
/blog/[slug]
):個別の記事内容を表示。
これらは、Next.jsのファイルベースのルーティング(例:pages/blog/index.js
やpages/blog/[slug].js
)と似ていますが、SvelteKitでは.svelte
ファイルでUIを直接記述できるため、より直感的です。
ブログ一覧ページの構築
1. モックデータの準備
まず、ブログ記事のモックデータを作成します。src/lib/data/posts.js
に以下を追加:
export const posts = [
{
slug: 'first-post',
title: '初めてのブログ記事',
excerpt: 'これはSvelteKitで作った最初の記事です。',
date: '2025-04-10',
content: 'ここに記事の全文が入ります。SvelteKitはシンプルで高速なフレームワークです。'
},
{
slug: 'second-post',
title: 'SvelteKitの魅力',
excerpt: 'SvelteKitの開発体験について語ります。',
date: '2025-04-11',
content: 'SvelteKitは、ReactやNext.jsと比べて記述量が少なく、初心者にも優しいです。'
}
];
このデータを使って、記事一覧を表示します。src/lib/
は、プロジェクト全体で再利用可能なコードを置く場所として便利です。
2. ブログ一覧ページの作成
src/routes/blog/+page.svelte
を作成し、以下を記述:
<script>
import { posts } from '$lib/data/posts';
import { Card, CardHeader, CardContent, CardFooter } from '@skeletonlabs/skeleton';
</script>
<div class="container mx-auto p-8">
<h1 class="h1 mb-8">ブログ記事一覧</h1>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
{#each posts as post}
<Card>
<CardHeader>
<h2 class="h3">{post.title}</h2>
<p class="text-sm text-surface-500">{post.date}</p>
</CardHeader>
<CardContent>
<p>{post.excerpt}</p>
</CardContent>
<CardFooter>
<a href="/blog/{post.slug}" class="btn variant-filled-primary">続きを読む</a>
</CardFooter>
</Card>
{/each}
</div>
</div>
-
解説:
-
posts
をインポートし、#each
ディレクティブで記事をループ表示。 -
Skeleton UIの
Card
コンポーネントを使って、記事をカード形式で表示。 - 各カードには、タイトル、投稿日、抜粋、詳細ページへのリンクを配置。
-
grid-cols-1 md:grid-cols-2
で、レスポンシブなグリッドレイアウトを適用。
-
3. Reactとの比較
Reactで同じ一覧を作る場合、以下のようにuseState
やmap
を使います:
// React
import { useState } from 'react';
const posts = [...]; // モックデータ
function BlogList() {
return (
<div className="container mx-auto p-8">
<h1>ブログ記事一覧</h1>
<div className="grid">
{posts.map(post => (
<div key={post.slug} className="card">
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
<a href={`/blog/${post.slug}`}>続きを読む</a>
</div>
))}
</div>
</div>
);
}
Reactでは、状態管理やJSXの記述量が多くなりがちです。一方、Svelteでは#each
やテンプレート構文がシンプルで、HTMLに近い感覚で書けるのが魅力です。
記事詳細ページの構築
1. ダイナミックルートの作成
記事詳細ページは、URLのslug
に基づいて表示します。src/routes/blog/[slug]/+page.svelte
を作成:
<script>
import { posts } from '$lib/data/posts';
import { error } from '@sveltejs/kit';
export let data;
const post = posts.find(p => p.slug === data.slug);
if (!post) {
throw error(404, '記事が見つかりません');
}
</script>
<div class="container mx-auto p-8">
<article class="card p-6 bg-surface-50">
<header class="mb-4">
<h1 class="h2">{post.title}</h1>
<p class="text-sm text-surface-500">{post.date}</p>
</header>
<section class="prose">
<p>{post.content}</p>
</section>
<footer class="mt-6">
<a href="/blog" class="btn variant-ghost-primary">一覧に戻る</a>
</footer>
</article>
</div>
-
解説:
-
export let data
で、SvelteKitが提供するslug
を受け取る(後述)。 -
posts
からslug
に一致する記事を検索。見つからない場合は404
エラーを投げる。 -
Skeleton UIの
card
クラスとprose
(Tailwindのタイポグラフィ)で、読みやすい記事ページを構築。
-
2. ルートデータの取得
SvelteKitでは、ダイナミックルートのために+page.js
(または+page.server.js
)でデータを準備できます。src/routes/blog/[slug]/+page.js
を作成:
export function load({ params }) {
return {
slug: params.slug
};
}
このload
関数は、URLの[slug]
部分をdata.slug
としてページに渡します。これにより、ページ側でslug
を使って記事を検索できます。
3. React/Next.jsとの比較
Next.jsで同様のダイナミックルートを作る場合、getStaticProps
やgetStaticPaths
が必要です:
// Next.js
export async function getStaticProps({ params }) {
const post = posts.find(p => p.slug === params.slug);
return { props: { post } };
}
export async function getStaticPaths() {
return {
paths: posts.map(post => ({ params: { slug: post.slug } })),
fallback: false
};
}
Next.jsでは、静的生成(SSG)のために追加の設定が必要ですが、SvelteKitのload
関数はシンプルで、動的データも静的データも統一的に扱える点が優れています。
動作確認
プロジェクトを起動します:
npm run dev
ブラウザで以下を確認:
-
http://localhost:5173/blog
:記事一覧ページ。カード形式で2つの記事が表示され、「続きを読む」ボタンが詳細ページにリンク。 -
http://localhost:5173/blog/first-post
:記事詳細ページ。タイトル、日付、本文が表示。
Skeleton UIのおかげで、UIはモダンでレスポンシブです。Svelteのリアクティブ変数(例:let post
)を使うことで、状態管理がシンプルになり、ReactのuseState
やuseEffect
を意識せずに済みます。
やってみよう!(チャレンジ)
記事一覧ページに簡単なフィルター機能を追加してみましょう!例えば、以下のようにタグをモックデータに追加:
// src/lib/data/posts.js
{
slug: 'first-post',
title: '初めてのブログ記事',
excerpt: 'これはSvelteKitで作った最初の記事です。',
date: '2025-04-10',
content: '...',
tags: ['Svelte', 'Tutorial']
}
そして、<select>
要素でタグを選択して記事をフィルタリングする機能を試してみてください。Svelteのリアクティブ変数を使うと、以下のように簡単に実装できます:
<script>
let selectedTag = '';
$: filteredPosts = selectedTag ? posts.filter(p => p.tags.includes(selectedTag)) : posts;
</script>
<select bind:value={selectedTag}>
<option value="">すべてのタグ</option>
<option value="Svelte">Svelte</option>
<option value="Tutorial">Tutorial</option>
</select>
このチャレンジで、Svelteの$:
(リアクティブステートメント)の力を体感してください!
まとめ
この記事では、SvelteKitのファイルベースのルーティングを活用し、ブログの記事一覧ページと記事詳細ページを構築しました。Skeleton UIで美しいUIを作り、Svelteのシンプルな構文でデータを表示しました。ReactやNext.jsと比べ、記述量が少なく、直感的な開発体験がSvelteKitの強みだと感じていただけたと思います。
次回は、Svelte-Queryを使って外部APIから動的な記事データを取得し、ブログをさらにリアルなアプリケーションに進化させます。お楽しみに!
この記事が役に立ったと思ったら、LGTMやストックしていただけると励みになります!質問やアイデアがあれば、ぜひコメントで教えてください。次の記事でまたお会いしましょう!