こんにちは!「SvelteKitで作るフルスタック個人ブログ」シリーズの第3回へようこそ。第2回では、SvelteKitのファイルベースのルーティングを活用し、ブログの記事一覧と詳細ページを静的データで構築しました。今回は、Svelte-Queryを使って外部APIから動的に記事データを取得し、ブログをリアルなアプリケーションに進化させます。React Queryとの比較も交えながら、SvelteKitとSvelte-Queryのスムーズな連携を体感しましょう!
この記事の目標
- Svelte-Queryの基本を理解し、APIデータの取得・キャッシュ管理を行う。
- 外部API(JSONPlaceholder)を活用して、ブログ記事を動的に表示する。
- ローディング状態やエラーハンドリングを簡単に実装する。
- Skeleton UIでユーザー体験(UX)を向上させる。
- React Queryとの違いを比較し、Svelte-Queryの開発体験(DX)の良さを確認する。
最終的には、静的データに依存せず、外部APIから記事データを取得してブログを動的に表示できるようにします。では、始めましょう!
Svelte-Queryとは?
Svelte-Queryは、Svelte向けのデータフェッチングライブラリで、React Queryにインスパイアされています。APIリクエストを簡単に管理し、以下のような機能を提供します:
- 自動キャッシュ:取得したデータをキャッシュして再利用。
- ローディング/エラー状態:状態管理を簡潔に処理。
- 自動リフェッチ:データが古くなった場合に自動更新。
React Queryを使ったことがある方は、Svelte-QueryのAPIが似ていると感じるでしょう。ただし、Svelteのリアクティブな特性を活かし、Svelteストアとの統合がスムーズです。Reactと比べて、ボイラープレートコードが少なく、直感的な記述が可能です。
Svelte-Queryのセットアップ
1. インストール
Svelte-Queryをプロジェクトに追加します。以下のコマンドを実行:
npm install @sveltestack/svelte-query
Svelte-Queryは、プロバイダーコンポーネントをセットアップする必要がある場合がありますが、今回は基本的なuseQuery
フックを直接使います。
2. APIの準備
この記事では、外部APIとしてJSONPlaceholderを使用します。JSONPlaceholderは、ブログ記事のようなデータを取得できる無料のモックAPIです。以下のエンドポイントを利用:
-
https://jsonplaceholder.typicode.com/posts
:記事一覧。 -
https://jsonplaceholder.typicode.com/posts/:id
:記事詳細。
試しに、ブラウザやcurl
でhttps://jsonplaceholder.typicode.com/posts
を開くと、以下のようなデータが返されます:
[
{
"userId": 1,
"id": 1,
"title": "sunt aut facere...",
"body": "quia et suscipit..."
},
...
]
このデータをブログ記事として扱います。
ブログ一覧ページの動的データ対応
1. 記事一覧の更新
第2回で作成したsrc/routes/blog/+page.svelte
を、Svelte-Queryを使って外部APIからデータを取得するように修正します。以下のように編集:
<script>
import { useQuery } from '@sveltestack/svelte-query';
import { Card, CardHeader, CardContent, CardFooter, Spinner } from '@skeletonlabs/skeleton';
const query = useQuery('posts', async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
if (!response.ok) throw new Error('データの取得に失敗しました');
return response.json();
});
</script>
<div class="container mx-auto p-8">
<h1 class="h1 mb-8">ブログ記事一覧</h1>
{#if $query.isLoading}
<div class="flex justify-center">
<Spinner />
</div>
{:else if $query.isError}
<p class="text-error-500">エラー: {$query.error.message}</p>
{:else}
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
{#each $query.data as post}
<Card>
<CardHeader>
<h2 class="h3">{post.title}</h2>
<p class="text-sm text-surface-500">投稿ID: {post.id}</p>
</CardHeader>
<CardContent>
<p>{post.body.slice(0, 100)}...</p>
</CardContent>
<CardFooter>
<a href="/blog/{post.id}" class="btn variant-filled-primary">続きを読む</a>
</CardFooter>
</Card>
{/each}
</div>
{/if}
</div>
-
解説:
-
useQuery
フックを使って、posts
キーでAPIデータを取得。 -
$query.isLoading
でローディング状態を表示(Skeleton UIのSpinner
を使用)。 -
$query.isError
でエラー状態を処理。 - データが取得できたら(
$query.data
)、記事をカード形式で表示。 - JSONPlaceholderの
id
をslug
として詳細ページにリンク。
-
2. React Queryとの比較
React Queryで同じ機能を実装する場合、以下のようなコードになります:
// React
import { useQuery } from 'react-query';
function BlogList() {
const { isLoading, isError, error, data } = useQuery('posts', async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
if (!response.ok) throw new Error('Failed to fetch');
return response.json();
});
if (isLoading) return <div>Loading...</div>;
if (isError) return <div>Error: {error.message}</div>;
return (
<div className="container">
{data.map(post => (
<div key={post.id} className="card">
<h2>{post.title}</h2>
<p>{post.body.slice(0, 100)}...</p>
<a href={`/blog/${post.id}`}>Read more</a>
</div>
))}
</div>
);
}
React Queryも強力ですが、状態管理(isLoading
, isError
)をJSX内で条件分岐する必要があります。一方、Svelte-QueryはSvelteのリアクティブストア($query
)を活用し、テンプレート内で条件(#if
)を自然に記述できます。これにより、コードがHTMLに近い感覚で書け、読みやすさが向上します。
記事詳細ページの動的データ対応
1. 詳細ページの更新
src/routes/blog/[slug]/+page.svelte
を以下のように修正:
<script>
import { useQuery } from '@sveltestack/svelte-query';
import { Spinner } from '@skeletonlabs/skeleton';
import { error } from '@sveltejs/kit';
export let data;
const query = useQuery(['post', data.slug], async () => {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${data.slug}`);
if (!response.ok) throw error(404, '記事が見つかりません');
return response.json();
});
</script>
<div class="container mx-auto p-8">
{#if $query.isLoading}
<div class="flex justify-center">
<Spinner />
</div>
{:else if $query.isError}
<p class="text-error-500">エラー: {$query.error.message}</p>
{:else}
<article class="card p-6 bg-surface-50">
<header class="mb-4">
<h1 class="h2">{$query.data.title}</h1>
<p class="text-sm text-surface-500">投稿ID: {$query.data.id}</p>
</header>
<section class="prose">
<p>{$query.data.body}</p>
</section>
<footer class="mt-6">
<a href="/blog" class="btn variant-ghost-primary">一覧に戻る</a>
</footer>
</article>
{/if}
</div>
-
解説:
-
useQuery
の第一引数に['post', data.slug]
を指定し、キャッシュキーを動的に設定。 -
data.slug
は、第2回で作成した+page.js
から渡される(以下参照)。 - ローディングやエラー状態を処理し、データが取得できたら記事を表示。
-
Skeleton UIの
Spinner
でローディングを視覚的に表現。
-
2. ルートデータの確認
src/routes/blog/[slug]/+page.js
は変更不要ですが、念のため確認:
export function load({ params }) {
return {
slug: params.slug
};
}
このload
関数は、URLの[slug]
(例:/blog/1
の1
)をdata.slug
としてページに渡します。
3. React/Next.jsとの比較
Next.jsで同様の詳細ページを作る場合、動的データを取得するためにgetServerSideProps
やgetStaticProps
を使います:
// Next.js
export async function getServerSideProps({ params }) {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${params.slug}`);
const post = await response.json();
return { props: { post } };
}
Next.jsでは、サーバーサイドでのデータ取得が強力ですが、クライアントサイドのキャッシュ管理には別途ライブラリ(React Queryなど)が必要です。一方、Svelte-Queryはクライアントサイドのデータフェッチを簡潔に処理し、SvelteKitのload
関数と自然に連携します。
動作確認
プロジェクトを起動します:
npm run dev
ブラウザで以下を確認:
-
http://localhost:5173/blog
:JSONPlaceholderから取得した記事一覧が表示。ローディング中はSpinner
が表示され、エラーがあればエラーメッセージが出る。 -
http://localhost:5173/blog/1
:記事詳細ページ。指定したIDの記事が表示され、同様にローディングやエラーを処理。
Svelte-Queryのおかげで、データフェッチングが簡単になり、Skeleton UIのコンポーネントがUXを向上させています。React Queryを使った場合に比べ、Svelte-QueryはSvelteのストアを活用することで、状態管理のコードが少なく、直感的な記述が可能です。
やってみよう!(チャレンジ)
記事一覧ページに「リフレッシュ」ボタンを追加して、データを手動で再取得してみましょう!Svelte-Queryのrefetch
関数を使えば簡単に実装できます。以下は例:
<CardFooter>
<a href="/blog/{post.id}" class="btn variant-filled-primary">続きを読む</a>
<button on:click={() => $query.refetch()} class="btn variant-ghost">リフレッシュ</button>
</CardFooter>
また、Svelte-Queryのドキュメント(公式サイト)を参考に、staleTime
やcacheTime
を設定してキャッシュの動作をカスタマイズしてみてください。これで、データ管理の柔軟性をさらに体感できます!
まとめ
この記事では、Svelte-Queryを使って外部API(JSONPlaceholder)からブログ記事データを取得し、動的に表示しました。ローディングやエラーハンドリングを簡単に実装し、Skeleton UIでユーザー体験を向上させました。React QueryやNext.jsと比べ、Svelte-QueryとSvelteKitの組み合わせは、少ないコードで直感的なデータフェッチングを実現します。
次回は、SvelteKitのAPIルートとDrizzle ORMを使って、独自のバックエンドを構築し、ブログ記事をデータベースに保存・取得する方法を解説します。お楽しみに!
この記事が役に立ったと思ったら、LGTMやストックしていただけると励みになります!質問や改善アイデアがあれば、コメントで教えてください。次の記事でまたお会いしましょう!