1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SvelteKitで作るフルスタック個人ブログ | 第2回: ブログのUI構築と静的データ処理

Posted at

こんにちは!「SvelteKitで作るフルスタック個人ブログ」シリーズの第2回へようこそ。第1回では、SvelteKitの概要を紹介し、Skeleton UITailwind 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.jspages/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 UICardコンポーネントを使って、記事をカード形式で表示。
    • 各カードには、タイトル、投稿日、抜粋、詳細ページへのリンクを配置。
    • grid-cols-1 md:grid-cols-2で、レスポンシブなグリッドレイアウトを適用。

3. Reactとの比較

Reactで同じ一覧を作る場合、以下のようにuseStatemapを使います:

// 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 UIcardクラスと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で同様のダイナミックルートを作る場合、getStaticPropsgetStaticPathsが必要です:

// 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のuseStateuseEffectを意識せずに済みます。


やってみよう!(チャレンジ)

記事一覧ページに簡単なフィルター機能を追加してみましょう!例えば、以下のようにタグをモックデータに追加:

// 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ストックしていただけると励みになります!質問やアイデアがあれば、ぜひコメントで教えてください。次の記事でまたお会いしましょう!

1
2
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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?