ऐप राउटर का डेटा फेचिंग तब आसान लगता है जब पुराना getServerSideProps मॉडल छोड़ दो। फेच वहीं करो जहाँ कंपोनेंट रहता है। डिफ़ॉल्ट सर्वर साइड है। कैश ऑप्ट-इन है (नेक्स्ट.जेएस १५ से)। क्लाइंट लाइब्रेरी इंटरैक्शन और ब्राउज़र-ओनली एपीआई के लिए हैं, पेज की हर लिस्ट के लिए नहीं।

यह पोस्ट वही नक्शा है जो मैं प्रोडक्शन प्रोजेक्ट पर इस्तेमाल करता हूँ: पहले सर्वर कंपोनेंट, साफ कैश नीति, समय या टैग से रिवैलिडेशन, और छोटी सूची जहाँ क्लाइंट फेच अभी भी सही फैसला है।


सर्वर कंपोनेंट डिफ़ॉल्ट डेटा पथ हैं

ऐप राउटर में page.tsx और ज़्यादातर कंपोनेंट सर्वर कंपोनेंट हैं जब तक 'use client' न लगाओ। मतलब वे async हो सकते हैं, fetch कॉल कर सकते हैं, डेटाबेस से बात कर सकते हैं, और सीक्रेट सर्वर पर रख सकते हैं।

// app/blog/page.tsx
export default async function BlogPage() {
  const res = await fetch("https://api.example.com/posts");
  const posts = await res.json();

  return (
    <ul>
      {posts.map((post: { id: string; title: string }) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

क्या मिलता है:

  • पहले पेंट के लिए उस डेटा का एपीआई राउंड-ट्रिप नहीं (एचटीएमएल/आरएससी भेजते समय सर्वर के पास डेटा पहले से है)।
  • सीक्रेट सर्वर पर रहते हैं (डीबी यूआरएल, प्राइवेट टोकन)।
  • सिर्फ दिखाने वाले ट्री पर कम क्लाइंट जेएस

केवल पेज डेटा लोड करने के लिए राउट हैंडलर की ज़रूरत नहीं। राउट हैंडलर दूसरे क्लाइंट की एचटीटीपी एपीआई, वेबहुक, या ब्राउज़र fetch टारगेट के लिए हैं। डेटा उसी सर्वर कंपोनेंट में लोड करो जो रेंडर करता है।

एक जैसी GET fetch कॉल (समान यूआरएल और विकल्प) एक रेंडर पास में मेमोइज़ होती हैं। लेआउट और पेज दोनों getUser() कॉल करें तो उस रिक्वेस्ट में नेटवर्क दो बार नहीं लगेगा।


कैश डिफ़ॉल्ट बदल चुका है। जान-बूझकर सेट करो।

नेक्स्ट.जेएस १४ में कई fetch कॉल डिफ़ॉल्ट से कैश होती थीं (force-cache शैली)। नेक्स्ट.जेएस १५+ में डिफ़ॉल्ट कैश न करने के करीब है (auto / डायनामिक राउट पर रिक्वेस्ट-टाइम अनकैश्ड)। अगर अपग्रेड के बाद "स्टैटिक" पेज हर रिक्वेस्ट पर एपीआई मारने लगे, कारण यही है।

हर रिक्वेस्ट पर नीति चुनो:

मकसद विकल्प
हमेशा ताज़ा cache: 'no-store' या next: { revalidate: 0 }
अमान्य होने तक कैश cache: 'force-cache' (वैकल्पिक टैग के साथ)
एन सेकंड कैश (आईएसआर शैली) next: { revalidate: 3600 }
कैश + नाम से अमान्य next: { tags: ['posts'] } और revalidateTag
// हमेशा ओरिजिन पर जाओ (यूज़र डैशबोर्ड, लाइव प्राइस, आदि)
const live = await fetch(url, { cache: "no-store" });

// एक घंटे कैश (मार्केटिंग कैटलॉग, डॉक्स इंडेक्स)
const catalog = await fetch(url, {
  next: { revalidate: 3600 },
});

// टैग या पाथ रिवैलिडेट होने तक सख्त कैश
const product = await fetch(url, {
  cache: "force-cache",
  next: { tags: ["product", `product-${id}`] },
});

टकराने वाले विकल्प मत मिलाओ। { cache: 'no-store', next: { revalidate: 3600 } } अमान्य है और नेक्स्ट जोड़ी को अनदेखा करता है (डेव में वार्निंग भी)।

राउट सेगमेंट कॉन्फिग (जब पूरा पेज एक व्यवहार रखे)

// इस सेगमेंट के लिए डायनामिक रेंडर जबरदस्ती
export const dynamic = "force-dynamic";

// या सेगमेंट की डिफ़ॉल्ट रिवैलिडेट विंडो
export const revalidate = 300;

कम इस्तेमाल करो। प्रति-fetch विकल्प बेहतर हैं ताकि एक धीमा लाइव विजेट पूरे मार्केटिंग पेज को डायनामिक न बना दे। जब लेआउट cookies() या headers() पढ़ता है, वह सेगमेंट (और अक्सर बच्चे) डायनामिक हो जाते हैं चाहे तुम न चाहो। ऑथ-संबंधी डेटा छोटी लीफ कंपोनेंट में रखो, रूट लेआउट में नहीं।


समय-आधारित रिवैलिडेट बनाम ऑन-डिमांड टैग

समय-आधारित (revalidate: N)

जब डेटा थोड़ा पुराना चल सकता है और हर राइट कंट्रोल में नहीं: सार्वजनिक ब्लॉग लिस्ट, डॉक्स, प्रोडक्ट ग्रिड जो घंटे में कुछ बार बदलता है।

const res = await fetch("https://api.example.com/posts", {
  next: { revalidate: 60 },
});

६० सेकंड बाद अगली रिक्वेस्ट रिवैलिडेशन ट्रिगर कर सकती है (डिप्लॉयमेंट के अनुसार स्टेल-व्हाइल-रिवैलिडेट शैली)। पाठक को तेज़ जवाब मिलता रहता है जबकि कैश बैकग्राउंड में ताज़ा होता है।

ऑन-डिमांड (tags + revalidateTag / revalidatePath)

जब जाना-माना राइट कैश साफ करे: सीएमएस पब्लिश, एडमिन एडिट, सर्वर एक्शन वाला फॉर्म।

// app/lib/posts.ts
export async function getPosts() {
  const res = await fetch("https://api.example.com/posts", {
    next: { tags: ["posts"], revalidate: 3600 },
  });
  return res.json();
}
// app/actions.ts
"use server";

import { revalidateTag, revalidatePath } from "next/cache";

export async function publishPost(formData: FormData) {
  await savePost(formData); // तुम्हारा डीबी/एपीआई राइट
  revalidateTag("posts");
  revalidatePath("/blog");
}
  • revalidateTag: सर्जिकल। product-42 टैग करो, बाकी कैटलॉग छोड़ो।
  • revalidatePath: मोटा। पाथ से जुड़े कैश्ड डेटा साफ करता है। जब सोच राउट में हो, डेटा डोमेन में नहीं।

स्केल करने वाले टैग नाम: बहुवचन रिसोर्स (posts, inventory) और वैकल्पिक इंस्टेंस टैग (post-${id})। जब डिटेल और लिस्ट दोनों बदलें तो राइट पर दोनों अमान्य करो।


डेटाबेस और ओआरएम: unstable_cache और React.cache

fetch कैशिंग सिर्फ fetch पर लागू होती है। प्रिज़्मा, ड्रिज़ल और कच्चा एसक्यूएल कुछ और माँगते हैं।

एक रिक्वेस्ट में डेड्यूप: React.cache

import { cache } from "react";
import { db } from "@/lib/db";

export const getUser = cache(async (id: string) => {
  return db.user.findUnique({ where: { id } });
});

एक रिक्वेस्ट, कई कंपोनेंट: एक क्वेरी। यह क्रॉस-रिक्वेस्ट कैश नहीं। अगला रेंडर पास साफ शुरू होता है।

क्रॉस-रिक्वेस्ट कैश: unstable_cache

import { unstable_cache } from "next/cache";
import { db } from "@/lib/db";

export const getCachedPosts = unstable_cache(
  async () => {
    return db.post.findMany({ orderBy: { createdAt: "desc" }, take: 20 });
  },
  ["posts-list"],
  { revalidate: 120, tags: ["posts"] }
);

महँगे रीड जो कई यूज़र साझा करते हैं, वहाँ लगाओ। म्यूटेशन के बाद भी revalidateTag("posts") कॉल करो। नाम अजीब है; २०२५ तक ज़्यादातर प्रोडक्शन ऐप नॉन-फेच कैश के लिए यही एपीआई इस्तेमाल करते रहे। की ऐरे को उस फंक्शन आकार की स्थिर पहचान मानो।


पैरेलल बनाम सीक्वेंशियल फेच

गलती से सीक्वेंशियल होना आम जाल है:

// गलत: दूसरा पहले का इंतज़ार करता है भले स्वतंत्र हो
const user = await getUser(id);
const feed = await getFeed(id);

दोनों शुरू करो, फिर अवेट:

const userPromise = getUser(id);
const feedPromise = getFeed(id);
const [user, feed] = await Promise.all([userPromise, feedPromise]);

जब बी को ए का आईडी चाहिए, उस किनारे सीक्वेंशियल रखो, बाकी सस्पेंस से स्ट्रीम करो ताकि पेज शेल धीमी शाखा का इंतज़ार न करे।

import { Suspense } from "react";

export default async function Page({
  params,
}: {
  params: Promise<{ username: string }>;
}) {
  const { username } = await params;
  const artist = await getArtist(username);

  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<p>Loading playlists...</p>}>
        <Playlists artistId={artist.id} />
      </Suspense>
    </>
  );
}

async function Playlists({ artistId }: { artistId: string }) {
  const playlists = await getPlaylists(artistId);
  return (
    <ul>
      {playlists.map((p) => (
        <li key={p.id}>{p.name}</li>
      ))}
    </ul>
  );
}

सेगमेंट में loading.tsx पेज को मुफ्त सस्पेंस में लपेटता है। धीमे हिस्सों पर सख्त बाउंडरी रखो ताकि स्टैटिक क्रोम जल्दी पेंट हो।


क्लाइंट फेच कब अभी भी सही है

सर्वर कंपोनेंट डिफ़ॉल्ट हैं। क्लाइंट फेच मना नहीं। तब इस्तेमाल करो जब ब्राउज़र को रिक्वेस्ट लाइफसाइकल का मालिक होना हो।

क्लाइंट फेच जब... सर्वर जब...
डेटा माउंट के बाद यूज़र इवेंट पर निर्भर (टाइप करते सर्च, इनफिनिट स्क्रोल) पहले पेंट को डेटा चाहिए
पोलिंग या वेबसॉकेट चालित यूआई सीक्रेट या प्राइवेट एपीआई
ब्राउज़र-ओनली एपीआई (जियोलोकेशन, लोकल टोकन जो सर्वर नहीं भेजना) एसईओ और क्रॉलर-दिखने वाली सामग्री
बहुत इंटरैक्टिव कैश (एसडब्ल्यूआर / रिएक्ट क्वेरी ऑप्टिमिस्टिक यूएक्स के लिए) साझा सार्वजनिक डेटा जिसकी रिवैलिडेट विंडो ज्ञात हो

पैटर्न: सर्वर बीज बोए, क्लाइंट ताज़ा रखे

// Server page
import { ProductClient } from "./product-client";

export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const product = await getProduct(id); // सर्वर, शायद force-cache + tags
  return <ProductClient initial={product} id={id} />;
}
// product-client.tsx
"use client";

import useSWR from "swr";

const fetcher = (url: string) => fetch(url).then((r) => r.json());

export function ProductClient({
  initial,
  id,
}: {
  initial: Product;
  id: string;
}) {
  const { data } = useSWR(`/api/products/${id}`, fetcher, {
    fallbackData: initial,
    revalidateOnFocus: true,
  });

  return <h1>{data.title}</h1>;
}

/api/products/... के पीछे राउट हैंडलर या सार्वजनिक एपीआई पर ऑथ लागू रहना चाहिए। सर्वर से प्रॉमिस क्लाइंट कंपोनेंट में पास कर रिएक्ट के use() से हल करना तब साफ स्ट्रीमिंग विकल्प है जब बाद में क्लाइंट रिवैलिडेशन न चाहिए।

क्या न करो

  • पूरा पेज 'use client' करके होमपेज ब्राउज़र से दोबारा फेच ("क्योंकि एसपीए ऐसे चलता था")।
  • "अस्थायी" एनव लीक से डीबी क्रेडेंशियल क्लाइंट बंडल में।
  • क्लाइंट से revalidateTag कॉल। यह सर्वर एक्शन या राउट हैंडलर में रहता है।

व्यावहारिक निर्णय वृक्ष

१. क्या पहले एचटीएमएल को यह डेटा चाहिए? सर्वर कंपोनेंट में फेच। २. यूज़र-विशिष्ट या सीक्रेट? सिर्फ सर्वर। कुकी/सेशन सर्वर पर पढ़ो। ३. एन सेकंड कई यूज़र साझा कर सकते हैं? revalidate: N या force-cache + टैग। ४. जानी-मानी म्यूटेशन से पुराना हो जाता है? टैग लगाओ और सर्वर एक्शन में revalidateTag कॉल करो। ५. fetch नहीं? रिक्वेस्ट डेड्यूप के लिए React.cache; क्रॉस-रिक्वेस्ट के लिए unstable_cache। ६. लोड के बाद अपडेट ब्राउज़र चलाए? क्लाइंट फेच / एसडब्ल्यूआर / रिएक्ट क्वेरी, वैकल्पिक रूप से सर्वर से सीडेड। ७. एक शाखा धीमी? पूरे राउट को ब्लॉक करने की जगह सस्पेंस / loading.tsx से काटो।


गलतियाँ जो अब भी दिखती हैं

१. अपग्रेड के बाद नेक्स्ट १४ कैश डिफ़ॉल्ट मान लेना। साझा डेटा हेल्पर में साफ cache / revalidate "यह हमेशा डायनामिक क्यों" डीबग के घंटे बचाता है। २. रूट लेआउट में cookies() और हैरानी कि बच्चे डायनामिक क्यों। ३. पर्सनलाइज़्ड जवाब साझा की पर कैश (एक यूज़र दूसरे की कार्ट देखे)। निजी डेटा: क्रॉस-यूज़र कैश नहीं, या सेशन आईडी से सावधानी से की। ४. राइट के बाद रिवैलिडेट भूलना। अकेले आईएसआर काफी नहीं अगर एडिटर तुरंत पब्लिश चाहें। ५. स्वतंत्र स्रोतों पर सीक्वेंशियल अवेट (लेआउट वॉटरफॉल)। ६. एसईओ-क्रिटिकल सामग्री पर क्लाइंट फेच जो सर्वर पर होनी चाहिए थी।


डेटा-भारी राउट शिप करने से पहले छोटी चेकलिस्ट

  • हर fetch पर जान-बूझकर कैश नीति।
  • म्यूटेशन जिन टैग/पाथ को छूते हैं उन पर revalidateTag / revalidatePath
  • एक रिक्वेस्ट में एक ही रो कई कंपोनेंट माँगें तो डीबी हेल्पर में React.cache
  • महँगी सार्वजनिक क्वेरी पर टैग के साथ unstable_cache (या बराबर)।
  • धीमे सेक्शन सस्पेंस के पीछे असली स्केलेटन के साथ, खाली पेज नहीं।
  • क्लाइंट कंपोनेंट इंटरैक्शन और लाइव रिफ्रेश के मालिक, सार्वजनिक सामग्री के पहले लोड के नहीं।
  • क्लाइंट बंडल में कोई सीक्रेट नहीं; ब्राउज़र जो राउट हैंडलर छू सकता है उन सब पर ऑथ चेक।

ऐप राउटर डेटा फेचिंग उबाऊ चुनावों को इनाम देता है: सर्वर पर लोड करो, बताओ नतीजा कितनी देर जी सकता है, राइट पर अमान्य करो, और क्लाइंट रिक्वेस्ट तभी खोलो जब ब्राउज़र सच में समयरेखा चलाए। जब यह विभाजन आदत बन जाए, कैश विकल्प जादू नहीं लगते, सामान्य इंफ्रा के घुंडी लगते हैं।