第81章:リクエストの重複を減らす考え方(同じデータは共有)🍱✨
この章のゴールはこれだよ〜!💡 **「同じデータが欲しいコンポーネントが何個あっても、ムダに同じ通信(or DB問い合わせ)を増やさない」**ようにすること🧠💕
1) ありがちな事故:同じユーザー情報を3回取りに行ってる😇📡📡📡
たとえば、こんな画面👇
- ヘッダーに「こんにちは、◯◯さん」😊
- 本文に「プロフィール」📄
- サイドバーに「アカウント」⚙️
全部「同じユーザー情報」が必要なのに、各コンポーネントがそれぞれ fetch("/api/me") したら…
同じリクエストが何回も飛んじゃうことがあるよね〜😭💸(遅くなる・料金や制限がキツい・ログがうるさい)
2) まず知っておきたい:Next.jsには「同じfetchは1回にまとめる」仕組みがある🍀
Next.js は fetch を拡張していて、**URLとオプションが同じ fetch を自動でメモ化(重複排除)**してくれるよ✅
だから、同じデータを複数箇所で fetch しても、レンダリング中は1回にまとまることがあるの✨ (Next.js)
しかもポイント!
{ cache: "no-store" } みたいに データキャッシュしない設定でも、「重複排除(メモ化)」自体は効く(=同じレンダーパス中の二重通信は避けられる)よ🍵 (Next.js)
ただし、URLやoptionsがちょっとでも違うと別物扱いになるよ⚠️(ヘッダー、クエリ、
next: { revalidate }なども含めて)
3) 図でつかむ:重複してる状態 vs 共有できてる状態🧩
❌ 重複してる状態(ムダが発生)💦
✅ 共有できてる状態(1回でみんな使う)🍱✨
4) 実戦テク:3つの「重複を減らす作法」🌟
作法A:データ取得を「1つの関数」に集める📦(最重要!)
同じデータを取りに行く処理を、あちこちに散らさないで
src/lib/ に 1本化しちゃうのが超おすすめ🥰
作法B:cache() で「同じ引数なら同じ結果」を保証する🍪
fetch 以外(DB/ORM/SDK)って、Nextの fetch 重複排除が効かないことがあるのね😵
そこで Reactの cache() を使うと、同じ引数の呼び出しは1回にまとまるよ✨
作法C:「同じURL/同じoptions」を守る🧷
同じに見えて、地味に違うと別リクエスト判定になるよ⚠️ 例:
?t=${Date.now()}を付けてる🧨(毎回違うURLになる)headersがコンポーネントごとに違う📮cache/nextオプションがバラバラ🌀
5) ハンズオン:ヘッダーとページで同じユーザーを共有する😊🪄
① src/lib/getUser.ts を作る📄
// src/lib/getUser.ts
import { cache } from "react";
export type User = {
id: number;
name: string;
username: string;
email: string;
};
// 同じ id なら結果(Promise)を共有してくれる✨
export const getUser = cache(async (id: number): Promise<User> => {
console.log("[getUser] fetching...", id); // サーバー側ログで回数チェック👀
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {
cache: "no-store", // 今回は「毎回新鮮」だけど、重複排除は効く🍵
});
if (!res.ok) throw new Error("Failed to fetch user");
return res.json();
});
② ヘッダー用コンポーネント components/UserHello.tsx 🍀
// components/UserHello.tsx
import { getUser } from "@/lib/getUser";
export default async function UserHello() {
const user = await getUser(1);
return (
<div style={{ padding: 12, borderBottom: "1px solid #ddd" }}>
こんにちは、<b>{user.name}</b> さん 😊🌸
</div>
);
}
③ ページ側 app/page.tsx 📄
// app/page.tsx
import { getUser } from "@/lib/getUser";
export default async function Page() {
const user = await getUser(1);
return (
<main style={{ padding: 12 }}>
<h1>プロフィール</h1>
<ul>
<li>name: {user.name}</li>
<li>username: {user.username}</li>
<li>email: {user.email}</li>
</ul>
</main>
);
}
④ レイアウトでヘッダーを表示 app/layout.tsx 🧱
// app/layout.tsx
import type { ReactNode } from "react";
import UserHello from "@/components/UserHello";
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="ja">
<body>
<UserHello />
{children}
</body>
</html>
);
}
✅ これで、ヘッダーとページが両方 getUser(1) を呼んでも
cache() のおかげで 同じレンダリング中は1回にまとまりやすいよ〜🍱✨
開発モードはHMRやリフレッシュ挙動の都合で、キャッシュ設定が期待通りに見えないこともあるよ(本番と完全に同じではない)👀 (Next.js)
6) よくある落とし穴(ここだけ見て!)⚠️🥺
- URLが毎回違う(例:
Date.now()、ランダム)🎲 → 共有できない - fetchのoptionsが違う(headers/next/cache)🧷 → 別リクエスト扱い
- 同じデータをServer Actionで取りに行ってる(POST扱い)📮 → 重複しやすい(※読み取りは基本
fetch/DB直で) - 「とりあえず no-store」乱用🔥 → “毎回取りに行く”ので、必要なところだけにしよ〜(この章は「重複排除」が主役!)
7) ミニ課題(5分)🧪🎀
components/UserMiniCard.tsxを作って、またgetUser(1)を呼ぶlayout.tsxに<UserMiniCard />を追加- ターミナルのサーバーログで
[getUser] fetching... 1が増えすぎないか観察👀✨
できたら勝ち〜!🎉🎉🎉