本PJではNext.js公式チュートリアルを元に実施します。
今回はchapter7の内容となります!
🚀 データを賢く取得!Next.jsの「サーバーコンポーネント」が変えるデータフェッチの新常識
Webアプリを作るとき、一番頭を悩ませるのが「どうやってデータを取ってくるか?」ですよね。
従来は、ページの表示後にuseEffectを使ってデータを取得し、useStateでローディング状態を管理する...といった複雑な手順が当たり前でした。
しかし、Next.jsのApp Routerでは、この常識がガラッと変わります。今回は、よりシンプルで、より高速なNext.jsのデータ取得戦略を見ていきましょう!
✨ Next.jsのデフォルト!「サーバーコンポーネント」という革命
Next.js (App Router) では、私たちが作るコンポーネントは デフォルトで「サーバーコンポーネント」 として動作します。これが超強力なんです。
- 魔法①:コンポーネントが
async/awaitを喋りだす! サーバーコンポーネントは、なんとコンポーネント関数自体をasyncにできます。
// page.tsx
export default async function Page() {
// もうuseEffectもuseStateもいらない!
const data = await fetchSomething();
return <div>{data.name}</div>;
}
- 魔法②:重い処理も"秘密"も全部サーバーにお任せ! このコンポーネントはサーバー側で実行されます。つまり...
-
安全!: データベースの認証情報(秘密の鍵)など、ブラウザに見せたくない情報をサーバー側に隠したままデータにアクセスできます。
-
高速!: 重いデータ処理や計算をすべてサーバー側で終わらせ、ブラウザには完成したHTML(結果)だけを送るので、ユーザーの端末(スマホやPC)に負荷がかかりません。
これは、レストランのキッチン(サーバー)でシェフが全ての調理(データ処理)を完璧に終え、完成した料理(HTML)だけをテーブル(クライアント)に運ぶようなものです。
🚚 ウェイターはもう不要?「API vs DB直接クエリ」
サーバーコンポーネントのおかげで、データの取得方法にも変化が起きました。
-
従来(APIレイヤー): ブラウザ(お客さん) → API(ウェイター) → データベース(厨房) 「APIを作る」という追加作業が必要でした。
-
Next.js (サーバーコンポーネント): サーバーコンポーネント(シェフ) → データベース(厨房の冷蔵庫) サーバー側で動くので、APIという中間層を挟まずに、データベースへ直接クエリを発行できます。これにより、作るファイルの数が減り、コード全体がシンプルになります。
(※もちろん、外部のサービスを使う場合や、クライアント側でデータを頻繁に更新する場合は、従来通りAPI(ルートハンドラ)を作ることも重要です。)
😱 陥りがちな罠!「リクエストウォーターフォール」にご用心
async/awaitが便利になったからといって、何も考えずに並べるとパフォーマンスの罠にハマることがあります。それがリクエストウォーターフォール(滝) です。
// page.tsx
export default async function Page() {
// ① 収益データを取得(例: 3秒かかる)
const revenue = await fetchRevenue();
// ② 最新の請求書を取得(例: 2秒かかる)
// ↑ fetchRevenue()の完了(3秒)を待ってから開始される
const latestInvoices = await fetchLatestInvoices();
// ③ カード情報を取得(例: 1秒かかる)
// ↑ fetchLatestInvoices()の完了(3+2=5秒)を待ってから開始される
const cardData = await fetchCardData();
// 全体の所要時間: 3 + 2 + 1 = 合計 6秒 😭
// ...
}
これは、おつかいを頼んだのに、1つ目のお店が終わるまで2つ目のお店に行ってくれないのと同じです。データ同士に関連性がない場合、これは非常に無駄な待ち時間になってしまいます。
✅ 解決策は「同時おつかい」!並列データフェッチ
このウォーターフォールを解決する魔法が、JavaScriptの標準機能であるPromise.all()です。
- アイデア: 「全部のおつかい(データ取得)を同時にスタートさせよう!」
// data.ts などでデータを取得する関数側
export async function fetchCardData() {
try {
// 3つのリクエストを「同時に」開始する!
const invoiceCountPromise = sql`SELECT COUNT(*) FROM invoices`;
const customerCountPromise = sql`SELECT COUNT(*) FROM customers`;
const invoiceStatusPromise = sql`SELECT ... FROM invoices`;
// 3つすべての完了を待つ
const data = await Promise.all([
invoiceCountPromise,
customerCountPromise,
invoiceStatusPromise,
]);
// ...
}
}
こうすることで、バラバラに実行していたリクエストを並列(同時)に処理できます。
先ほどの例で言えば、3秒、2秒、1秒かかる処理を同時に開始すれば、全体は一番時間のかかる処理(3秒)で完了します。6秒かかっていた処理が3秒に短縮されるのです!
(ただし、Promise.all()にも「一番遅いリクエストに全員が待たされる」という弱点はありますが、逐次実行よりは遥かに高速です。)
今回はここまで! ご覧いただきありがとうございます!