Next.js के साथ Resilient सिस्टम बनाना
आधुनिक वेब एप्लिकेशन में उच्च उपलब्धता और प्रदर्शन सुनिश्चित करने की सर्वोत्तम प्रथाएँ।
TL;DR5
- टूलिंग से पहले आर्किटेक्चर: resilience एक डिज़ाइन गुण है
- stale-while-revalidate के साथ ISR बैकएंड आउटेज में भी पेज ज़िंदा रखता है
- रिट्राई को backoff और jitter चाहिए — circuit breaker कैस्केडिंग फेल्योर रोकता है
- Error boundary विफलता को पूरे पेज नहीं, टूटे widget तक सीमित रखती है
- Fallback UI और ऑफलाइन टॉलरेंस पैच नहीं, फर्स्ट-क्लास फीचर हैं

पहले आर्किटेक्चर
Next.js अक्सर आधुनिक वेब एप्लिकेशन की रीढ़ की हड्डी होता है। Resilience सुनिश्चित करने के लिए अच्छे आर्किटेक्चरल निर्णयों और सही टूलिंग का मिश्रण चाहिए।
Server Components और सही error boundaries का उपयोग करके, आप ऐसे वेब एप्लिकेशन बना सकते हैं जो पूरी तरह क्रैश होने के बजाय graceful degrade करते हैं।
Static Generation vs SSR
जब भी संभव हो, Incremental Static Regeneration (ISR) को डिफ़ॉल्ट बनाएं। यह गारंटी देता है कि भले ही आपके backend API अस्थायी रूप से डाउन हों, उपयोगकर्ता अभी भी पेज का आखिरी सफलतापूर्वक बिल्ड किया गया संस्करण देखेंगे।
लेकिन ट्रेडऑफ़ असली है: ISR ताज़गी की कीमत पर resilience देता है। SSR ताज़गी देता है लेकिन backend पर कठोर निर्भरता की कीमत पर।
// app/blog/[slug]/page.tsx
export const revalidate = 300; // 5 मिनट
export default async function Post({ params }) {
const post = await fetchPost(params.slug); // cached + revalidated
return <Article post={post} />;
}
पिछले साल एक Cloudflare इंसिडेंट के दौरान जब मेरा origin API 40 मिनट के लिए डाउन था, y7.hk के सभी ISR पेज पुराने-लेकिन-मान्य HTML परोसते रहे। उसी साइट के SSR पेज 500 लौटा रहे थे। उस प्रयोग ने मुझे मासिक ट्रैफ़िक का 4% खर्च किया — और रेंडरिंग रणनीति के बारे में मेरी सोच को स्थायी रूप से बदल दिया।
अंगूठे का नियम: जो 5–60 मिनट पुराना हो सकता है उसके लिए ISR (ब्लॉग पोस्ट, प्रोडक्ट पेज, मार्केटिंग)। SSR केवल तब जब पुरानापन प्रोडक्ट तोड़ दे (लाइव डैशबोर्ड, ऑथ स्टेट, रीयल-टाइम डेटा)।
Error Boundary आइसोलेशन बल्कहेड हैं
Boundary के बिना, एक फेल होता widget पूरा पेज गिरा देता है। Boundary के साथ, हर सेक्शन स्वतंत्र रूप से फेल होता है।
// app/components/Section.tsx
'use client';
import { Component, ReactNode } from 'react';
export class Section extends Component<
{ children: ReactNode; fallback?: ReactNode },
{ hasError: boolean }
> {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return this.props.fallback ?? <div className="opacity-50">यह सेक्शन उपलब्ध नहीं है</div>;
}
return this.props.children;
}
}
हर स्वतंत्र इकाई — साइडबार विजेट, कमेंट सेक्शन, एनालिटिक्स डैशबोर्ड — को अपने boundary में लपेटें। उपयोगकर्ता खाली स्क्रीन के बजाय विजेट की जगह एक ग्रे बॉक्स देखता है।
मैंने यह कड़वे अनुभव से सीखा जब एक Web3 डैशबोर्ड पर एक RPC timeout ने पूरा portfolio view क्रैश कर दिया। Price ticker के चारों ओर एक boundary सेशन बचा लेता।
Retry को Backoff चाहिए
अंधे retry फेल्योर को बढ़ाते हैं। Jitter के साथ एक्सपोनेंशियल backoff लोड फैलाता है और upstream को रिकवर होने का समय देता है।
async function fetchWithRetry<T>(
fn: () => Promise<T>,
attempts = 3,
baseDelay = 300
): Promise<T> {
for (let i = 0; i < attempts; i++) {
try {
return await fn();
} catch (err) {
if (i === attempts - 1) throw err;
const jitter = Math.random() * baseDelay;
await new Promise(r => setTimeout(r, baseDelay * 2 ** i + jitter));
}
}
throw new Error('unreachable');
}
मैं दो नियम मानता हूं:
- कभी 4xx retry मत करो। 404 retry से 200 नहीं बनेगा।
- कुल प्रतीक्षा सीमित करो। अगर उपयोगकर्ता 8+ सेकंड से spinner देख रहा है, fallback UI दिखाओ और आगे बढ़ने दो।
Circuit Breaker कैस्केडिंग फेल्योर रोकता है
Retry ट्रांजिएंट एरर ठीक करते हैं। Circuit breaker परसिस्टेंट एरर ठीक करते हैं। अगर backend 30 सेकंड से फेल हो रहा है, उसे पीटना बंद करो — circuit खोलो और तुरंत degrade रिस्पॉन्स दो।
class Breaker {
private failures = 0;
private openUntil = 0;
constructor(private threshold = 5, private cooldown = 30_000) {}
async call<T>(fn: () => Promise<T>): Promise<T> {
if (Date.now() < this.openUntil) throw new Error('Circuit open');
try {
const result = await fn();
this.failures = 0;
return result;
} catch (e) {
if (++this.failures >= this.threshold) {
this.openUntil = Date.now() + this.cooldown;
}
throw e;
}
}
}
Breaker के बिना, धीमा upstream अटके हुए Node प्रोसेस का झुंड बन जाता है। Breaker के साथ, आपको तेज़ फेल्योर और एक ऐसा सिस्टम मिलता है जो upstream लौटते ही रिकवर हो जाता है।
stale-while-revalidate के साथ Edge Caching
Cache-Control हेडर आपकी सबसे सस्ती resilience लेयर है। stale-while-revalidate edge को आखिरी कैश्ड कॉपी परोसने देता है, जबकि बैकग्राउंड में ताज़ा कॉपी फ़ेच होती है।
Cache-Control: public, s-maxage=60, stale-while-revalidate=600
यह “API डाउन तो साइट डाउन” और “API डाउन तो कुछ उपयोगकर्ता 10 मिनट पुराना डेटा देखते हैं” के बीच का अंतर है।
Fallback UI एक फीचर है
Fallback वह नहीं है जो कुछ टूटने पर दिखता है — वह डिज़ाइन का हिस्सा है। हर async सेक्शन की तीन स्थितियां हैं:
- Loading: अंतिम लेआउट से मेल खाता skeleton (कोई layout shift नहीं)।
- Success: असली कंटेंट।
- Error: एक degrade वर्शन जो उपयोगकर्ता को अभी भी काम करने देता है।
अगर आपका pricing widget फेल होता है, लाल एरर मत दिखाओ — आखिरी कैश्ड कीमतें “12 मिनट पहले अपडेट हुआ” बैज के साथ दिखाओ। अगर comment system फेल होता है, read-only थ्रेड दिखाओ और input box छुपाओ।
ऑफलाइन टॉलरेंस
Service Worker सिर्फ PWA के लिए नहीं हैं। App shell और आखिरी 5 विज़िट किए पेज कैश करने वाला मिनिमल SW, लड़खड़ाते होटल wifi और 10,000 मीटर की उड़ान पर असली ऑफलाइन टॉलरेंस देता है — जो मेरे लिए ब्राउज़िंग का एक अच्छा-खासा हिस्सा है।
// sw.ts — स्टेटिक के लिए cache-first, पेज के लिए network-first
self.addEventListener('fetch', (e) => {
const url = new URL(e.request.url);
if (url.pathname.startsWith('/_next/static/')) {
e.respondWith(caches.match(e.request).then(r => r ?? fetch(e.request)));
}
});
असली फेल्योर जो मैंने झेले
- Cloudflare edge KV आउटेज: KV पर निर्भर हर पेज ने 500 लौटाया। ISR पेज बच गए।
- ट्रैफ़िक स्पाइक के दौरान upstream CMS ने मुझे rate-limit किया। Breaker नहीं था; Next.js ने CMS को तब तक पीटा जब तक मुझे हार्ड IP बैन न मिला। अब हर external call 30 सेकंड cooldown वाले breaker से गुज़रता है।
- WebSocket गिरने पर एक
<PriceTicker>ने पूरा डैशबोर्ड क्रैश कर दिया। Boundary जोड़ने के बाद बाकी UI ज़िंदा रहा जबकि ticker चुपचाप reconnect हुआ।
Resilience चेकलिस्ट
- 5+ मिनट पुराने होने वाले कंटेंट के लिए ISR/SSG
- हर cacheable रूट पर
stale-while-revalidate - हर स्वतंत्र widget के चारों ओर error boundary
- Retry पर exponential backoff + jitter; 4xx कभी retry न करें
- हर external service call पर circuit breaker
- हर async UI के लिए skeleton + fallback + error स्टेट
- App shell कैश करने वाला service worker
- गैर-महत्वपूर्ण फीचर (एनालिटिक्स, रिकमेंडेशन) के लिए दस्तावेज़ित kill-switch
मुद्दा
Resilience अंत में जोड़ा जाने वाला फीचर नहीं है। यह खुद आर्किटेक्चर है। हर लेयर — रेंडरिंग, कैशिंग, फ़ेचिंग, UI — को शुरू से डिज़ाइन किया गया failure mode मिलता है। इन्हें जल्दी बनाओ, और जो इंसिडेंट आउटेज होते, वे चुपचाप degrade बन जाते हैं जिनका किसी को पता नहीं चलता।
ब्लॉग से और भी
y7.hk पर आधुनिक इंजीनियरिंग लेख की एनाटॉमी
y7 रीडिंग इंजन का स्ट्रेस टेस्ट: कॉलआउट, diff ब्लॉक, KaTeX फ़ॉर्मूला, गोल अवतार वीडियो, figure गैलरी, स्पॉइलर और फ़ॉन्ट स्विचर।
टाइम ज़ोन के बीच जीवन
67 देश और चार होम बेस डीप वर्क, जेट लैग और ऐसे प्रोडक्ट बनाने के बारे में क्या सिखाते हैं जिन्हें किसी ने नहीं माँगा।
