- Next.js की भविष्य की दिशा रोचक लगती है
- Server Actions को लेकर कुछ issues थे, लेकिन React 19 के
useOptimistic, useFormStatus से सुधार की संभावना दिखती है
- Remix का
useFetcher तरीका भी अच्छा DX देता है
- Next.js का PPR(Partial Pre-rendering) और नया granular cache system खास तौर पर उभरकर सामने आता है
- कुल मिलाकर बहुत सकारात्मक प्रभाव पड़ा
The Big Picture
next.config.js में नए cache system को experimental रूप से enable किया जा सकता है
- cache profiles को define करके अलग-अलग expiration time और revalidation cycle सेट किए जा सकते हैं
// next.config.js
const config = {
experimental: {
// नया caching system enable करें. अब code में `use cache` इस्तेमाल किया जा सकता है
dynamicIO: true,
// optional: cache profiles configure करें
cacheLife: {
blog: {
stale: 3600, // client cache बनाए रखें: 1 घंटा
revalidate: 900, // server पर refresh: 15 मिनट
expire: 86400, // अधिकतम lifetime: 1 दिन
},
},
},
};
use cache का बुनियादी उपयोग
- file, component, function स्तर पर
"use cache" declaration के जरिए caching की जा सकती है
- code examples में
use cache जोड़कर आसानी से cache लागू किया जा सकता है
cacheTag, revalidateTag आदि का उपयोग करके मनचाहे समय पर cache invalidation किया जा सकता है
// 1. file-level caching
"use cache";
export default function Page() {
return <div>Cached Page</div>;
}
// 2. component-level caching
export async function PriceDisplay() {
"use cache";
const price = await fetchPrice();
return <div>${price}</div>;
}
// 3. function-level caching
export async function getData() {
"use cache";
return await db.query();
}
tag-based caching
import { unstable_cacheTag as cacheTag, revalidateTag } from 'next/cache';
// किसी खास data group को cache करना
export async function ProductList() {
'use cache';
cacheTag('products');
const products = await fetchProducts();
return <div>{products}</div>;
}
// data बदलने पर cache invalidate करना
export async function addProduct() {
'use server';
await db.products.add(...);
revalidateTag('products');
}
custom cache profiles
unstable_cacheLife का उपयोग करके next.config.js में define किए गए cache profiles को लाया जा सकता है
- code के भीतर declare किए गए profile name (जैसे
"blog") का उपयोग करके cache policy लागू की जाती है
import { unstable_cacheLife as cacheLife } from "next/cache";
export async function BlogPosts() {
"use cache";
cacheLife("blog"); // पहले से define किया गया blog cache profile इस्तेमाल करें
return await fetchPosts();
}
महत्वपूर्ण लेकिन आसानी से छूट जाने वाली बातें
cache key का automatic generation
- component के
props और arguments अपने-आप cache key में शामिल हो जाते हैं
- non-serializable values (जैसे functions) को "immutable reference" के रूप में handle किया जाता है
export async function UserCard({ id, onDelete }) {
"use cache";
// id cache key में शामिल होता है
// onDelete पास तो होता है, लेकिन caching को प्रभावित नहीं करता
const user = await fetchUser(id);
return <div onClick={onDelete}>{user.name}</div>;
}
dynamic content और cached content का मिश्रण
- cached content के अंदर dynamic content को children के रूप में पास करके दोनों को मिलाकर इस्तेमाल किया जा सकता है
cacheTag array specify करके कई tags एक साथ apply और invalidate किए जा सकते हैं
export async function CachedWrapper({ children }) {
"use cache";
const header = await fetchHeader();
return (
<div>
<h1>{header}</h1>
{children} {/* dynamic content वैसे ही बना रहता है */}
</div>
);
}
export async function ProductPage({ id }) {
"use cache";
cacheTag(["products", `product-${id}`, "featured"]);
// इनमें से किसी भी tag का उपयोग करके invalidation किया जा सकता है
}
caching hierarchy
- top-level पर
"use cache" declare करने से वह पूरा क्षेत्र cache हो जाता है
- कुछ specific हिस्सों (जैसे Suspense का उपयोग करने वाले dynamic sections) को caching क्षेत्र से बाहर रखा जा सकता है
"use cache";
export default async function Page() {
return (
<div>
<CachedHeader />
<div>
<Suspense fallback={<Loading />}>
<DynamicFeed /> {/* dynamic content */}
</Suspense>
</div>
</div>
);
}
type safety
- cache keys और cache profiles जैसी strings को constants के रूप में manage करके magic strings का उपयोग कम किया जा सकता है
- React Query के pattern की तरह tags generate करने वाला तरीका इस्तेमाल करें तो यह सुविधाजनक होता है
// cache profile keys को constants के रूप में manage करें
export const CACHE_LIFE_KEYS = {
blog: "blog",
} as const;
const config = {
experimental: {
cacheLife: {
[CACHE_LIFE_KEYS.blog]: {
stale: 3600,
revalidate: 900,
expire: 86400,
},
},
},
};
caching tags को efficiently manage करने का तरीका
- React Query style tag factory pattern लागू करना
export const CACHE_TAGS = {
blog: {
all: ["blog"] as const,
list: () => [...CACHE_TAGS.blog.all, "list"] as const,
post: (id: string) => [...CACHE_TAGS.blog.all, "post", id] as const,
comments: (postId: string) =>
[...CACHE_TAGS.blog.all, "post", postId, "comments"] as const,
},
} as const;
// caching tags सेट करना
function tagCache(tags: string[]) {
cacheTag(...tags);
}
// उपयोग उदाहरण
export async function BlogList() {
"use cache";
tagCache(CACHE_TAGS.blog.list());
}
3 टिप्पणियां
मुझे लगता है कि Next.js या Remix जैसे framework का इस्तेमाल केवल उन स्थितियों में करना बेहतर है जहाँ SEO महत्वपूर्ण हो और इसलिए SSR की ज़रूरत हो।
खासकर B2B बिज़नेस प्रोडक्ट या back office जैसी उन सेवाओं में, जहाँ SEO महत्वपूर्ण नहीं है, Next.js को अपनाने में सावधानी बरतने की ज़रूरत है। क्योंकि Next.js द्वारा थोपे गए interface या उसकी complexity डेवलपमेंट productivity को कम कर सकती है।
मेरी व्यक्तिगत राय में, जिन मामलों में SEO की ज़रूरत नहीं होती, वहाँ Vite + React डेवलपमेंट productivity और flexibility आदि के मामले में कहीं बेहतर है।
Next.js, 13 के बाद से काफ़ी उपयोगी हो गया है, लेकिन हाल में यह मुझे सच में बहुत-बहुत पसंद आ रहा है। लगता है कि यह full-stack web development tech stack का वास्तविक मानक बन जाएगा।