Next.jsにおけるデータ取得のマスター
Min-jun Kim
Dev Intern · Leapcell

Next.jsにおけるデータ取得のマスター
フロントエンド開発の進化し続ける状況において、効率的なデータ取得は、パフォーマンスが高くユーザーフレンドリーなアプリケーションを構築するために不可欠です。人気のReactフレームワークであるNext.jsは、データ取得とレンダリングのための強力な戦略を提供することで、この分野で継続的に革新を続けています。React Server Componentsの登場とそのNext.jsへの統合により、データ取得のパラダイムは大きく変化しました。これらの新しいアプローチ、特にクライアントコンポーネント、サーバーコンポーネント、および強化されたfetch
APIの間の区別と相互作用を理解することは、モダンで高度に最適化されたWebアプリケーションを構築することを目指す開発者にとって不可欠です。この記事では、これらの不可欠な概念を探求し、Next.jsでのデータ取得をマスターし、優れたユーザーエクスペリエンスを提供するための実践的なガイダンスとコード例を提供します。
コアコンセプトと戦略
実装の詳細に入る前に、Next.jsのデータ取得戦略の基盤となるコアコンセプトを明確に理解しておきましょう。
クライアントコンポーネントとサーバーコンポーネント
クライアントコンポーネント(CC):これらは、ユーザーのブラウザ内でクライアントサイドで完全に実行され、再レンダリングされる、おなじみのReactコンポーネントです。useState
やuseEffect
のようなReact Hooksを、インタラクティビティや副作用のために活用できます。データ取得の場合、クライアントコンポーネントは通常、useEffect
をaxios
、swr
、react-query
のようなライブラリ、またはネイティブのfetch
APIと組み合わせて使用します。
サーバーコンポーネント(SC):画期的なイノベーションであるサーバーコンポーネントは、サーバー上で排他的に実行されます。リクエスト中(またはビルド時)に一度レンダリングされ、結果のHTMLと必要なバンドルをクライアントに送信します。サーバー上で実行されるため、サーバーコンポーネントは、データベース、ファイルシステム、またはプライベートAPIのようなサーバーサイドリソースに、機密情報をクライアントに公開することなく直接アクセスできます。状態や副作用を持たず、ユーザーインタラクションを直接処理することはできません。それらの主なユースケースには、データ取得、サーバーサイドロジックへのアクセス、クライアントサイドインタラクションを必要としない静的または動的コンテンツのレンダリングが含まれます。
強化されたfetch
API
Next.jsは、特にサーバーコンポーネント内で使用される場合に、強力な機能でネイティブのfetch
APIを強化します。これらの強化には、自動リクエストのメモ化、next/cache
を介した再検証、およびコンポーネント定義でのasync/await
のサポートが含まれます。この統合により、組み込みのキャッシュと再検証ロジックでデータ取得を合理化する方法が提供されます。
クライアントコンポーネントでのデータ取得
アプリケーションのインタラクティブな部分や、ユーザーアクションに基づいたリアルタイム更新が必要な場合は、クライアントコンポーネントを使用します。通常、初期レンダリング後にuseEffect
を使用してデータを取得します。
// components/ClientDataFetcher.jsx 'use client'; // This directive marks it as a Client Component import { useState, useEffect } from 'react'; export default function ClientDataFetcher() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { async function fetchData() { try { const response = await fetch('/api/public-data'); // Fetches from a public API endpoint if (!response.ok) { throw new Error('Failed to fetch data'); } const json = await response.json(); setData(json); } catch (err) { setError(err); } finally { setLoading(false); } } fetchData(); }, []); // Empty dependency array ensures it runs once on mount if (loading) return <p>Loading client data...</p>; if (error) return <p>Error: {error.message}</p>; return ( <div> <h2>Client-Fetched Data</h2> <pre>{JSON.stringify(data, null, 2)}</pre> </div> ); }
この例では、'use client'
ディレクティブがClientDataFetcher
をクライアントコンポーネントとして明確に識別しています。データは、コンポーネントがマウントされた後にuseEffect
を使用して取得され、インタラクティビティとクライアントサイドイベントに基づいたデータ更新の能力を保証します。
サーバーコンポーネントでのデータ取得
サーバーコンポーネントは、コンポーネントがクライアントに送信される前にサーバー上で直接データを取得するのに優れています。これにより、パフォーマンスの向上(データのための追加のネットワークラウンドトリップなし)、セキュリティの強化(APIキーの公開なし)、コードの簡素化など、いくつかの利点が得られます。
// app/page.jsx (This is a Server Component by default in the App Router) // For RSCs, you can make the component async directly async function getServerData() { // Using an internal API route for example, or directly connect to a database const response = await fetch(`${process.env.API_BASE_URL}/api/products`, { cache: 'no-store', // Opt-out of caching for this request // next: { revalidate: 60 } // Revalidate this data every 60 seconds }); if (!response.ok) { throw new Error('Failed to fetch server data'); } return response.json(); } export default async function HomePage() { const products = await getServerData(); // Data fetched on the server return ( <div> <h1>Welcome to Next.js Store!</h1> <h2>Our Products (Server-Fetched)</h2> <ul> {products.map(product => ( <li key={product.id}> {product.name} - ${product.price} </li> ))} </ul> {/* Client components can be rendered within Server Components */} {/* For example, an "Add to Cart" button would be a Client Component */} {/* <AddToCartButton productId={product.id} /> */} </div> ); }
ここでは、HomePage
はサーバーコンポーネントです(app
ディレクトリ内の場所と'use client'
ディレクティブがないことから推測されます)。getServerData
関数はコンポーネント内で直接呼び出され、レンダリング前にその結果を待機します。cache: 'no-store'
またはnext: { revalidate: 60 }
(時間ベースの再検証用)のようなfetch
オプションは、Next.js内でのfetch
の強化された機能を示しています。cache: 'no-store'
はデータが常に各リクエストで取得されることを意味し、revalidate
はバックグラウンドでの再検証を可能にします。
クライアントとサーバーコンポーネントの組み合わせ
Next.jsの強みは、クライアントコンポーネントとサーバーコンポーネントをシームレスに組み合わせる能力にあります。サーバーコンポーネントはクライアントコンポーネントをレンダリングし、データをプロップとして渡すことができます。これにより、静的または頻繁にアクセスされるデータをサーバーで取得しつつ、必要な場所でリッチなインタラクティビティを提供できます。
// app/product/[id]/page.jsx (Server Component for dynamic routing) import AddToCartButton from '@/components/AddToCartButton'; // This is a Client Component async function getProductDetails(id) { const response = await fetch(`${process.env.API_BASE_URL}/api/products/${id}`); if (!response.ok) { throw new Error(`Failed to fetch product ${id}`); } return response.json(); } export default async function ProductDetailsPage({ params }) { const product = await getProductDetails(params.id); return ( <div> <h1>{product.name}</h1> <p>{product.description}</p> <p>Price: ${product.price}</p> {/* Passing server-fetched data as props to a Client Component */} <AddToCartButton productId={product.id} productName={product.name} /> </div> ); } // components/AddToCartButton.jsx 'use client'; import { useState } from 'react'; export default function AddToCartButton({ productId, productName }) { const [isLoading, setIsLoading] = useState(false); const [message, setMessage] = useState(''); const handleAddToCart = async () => { setIsLoading(true); setMessage(''); try { // Simulate API call to add to cart await new Promise(resolve => setTimeout(resolve, 1000)); // In a real app, you'd make a fetch POST request here setMessage(`${productName} added to cart!`); } catch (error) { setMessage('Failed to add to cart.'); } finally { setIsLoading(false); } }; return ( <button onClick={handleAddToCart} disabled={isLoading}> {isLoading ? 'Adding...' : 'Add to Cart'} {message && <p>{message}</p>} </button> ); }
このセットアップでは、ProductDetailsPage
(サーバーコンポーネント)が製品データを取得します。その後、productId
とproductName
をプロップとして渡しながら、AddToCartButton
(クライアントコンポーネント)をレンダリングします。ボタン自体はクライアントサイドの状態とインタラクションを処理し、クリック時にAPI呼び出しを行います。このパターンは、両方のコンポーネントタイプの強みを活用します。
結論
Next.jsは、開発者に高度に最適化され、回復力のあるWebアプリケーションを構築するためのツールを提供する、強力で柔軟なデータ取得戦略の配列を提供します。クライアントコンポーネントとサーバーコンポーネントの間のコアな違いを理解し、強化されたfetch
APIを活用することで、データの取得場所と方法について情報に基づいた決定を下すことができます。このアプローチは、パフォーマンスの向上、セキュリティの向上、および優れたユーザーエクスペリエンスにつながり、Next.jsをモダンなWeb開発に最適な選択肢としています。