< BACK मैंने Next.js में 25,000-पेज की डायरेक्टरी कैसे बनाई: HostList Post-Mortem -- लाइन-आर्ट इलस्ट्रेशन

मैंने Next.js में एक 25,000-पेज की डायरेक्टरी कैसे बनाई: HostList पोस्ट-मॉर्टम

2022 के आखिरी में मैंने खुद को समझा लिया कि एक web hosting directory बनाना सीधा होगा। डेटा जमा करो, पेज जेनरेट करो, रैंक करो, monetise करो। साफ-सुथरा। मैंने पहले programmatic SEO करी थी — एक UK क्लाइंट के लिए localized real estate tool, एक SaaS comparison site जो 40k monthly visits तक पहुँची — तो मुझे लगा कि HostList छह हफ्ते की प्रोजेक्ट होगी। यह सात महीने के करीब चली। और इसने कुछ चीजों को लगभग तोड़ दिया: मेरी sleep schedule को, एक junior dev की confidence को, और एक £180/month Vercel बिल को जिसके लिए मैंने budget नहीं रखा था।

यह पोस्ट-मॉर्टम है। असली वाला, LinkedIn वाला नहीं।

---

ब्रीफ जो मैंने खुद के लिए लिखा

HostList सीधी चीज़ थी। Web hosting providers की एक directory — shared, VPS, dedicated, managed WordPress — हर provider के लिए individual pages, comparison pages, category pages, और location-based pages (मसलन "best hosting in Germany")। गणित करो: ~400 providers × कई page types × 20+ filter combinations। तुम 25,000 पेजों तक सोचते हो उससे जल्दी पहुँच जाते हो।

मैंने Next.js को लगभग सोचे-समझे बिना ही चुन लिया। हम Seahawk में इसका इस्तेमाल अपनी ज्यादातर बड़ी React-आधारित बिल्ड के लिए करते हैं। इकोसिस्टम परिपक्व है, getStaticProps और getStaticPaths SEO-भारी स्टैटिक जनरेशन के लिए समझदारी रखते हैं, और मुझे व्यक्तिगत रूप से फाइल-आधारित राउटिंग को Remix या Gatsby से इस स्केल पर समझना आसान लगता है।

पहला असली फैसला data layer का था। मैंने headless CMS को जल्दी खारिज कर दिया — मैं 25,000 entries के लिए Contentful rates नहीं देना चाहता था, और मैं किसी CMS पर bulk programmatic writes को cleanly handle करने का भरोसा नहीं रखता था। हम Supabase पर Postgres database पर उतरे, जिसके सामने एक lightweight Next.js API layer बैठा। वह हिस्सा असल में ठीक काम करा। बाकी सब कुछ complicated हो गया।

---

Static Generation at Scale: What Nobody Warns You About

25,000 राउट्स के साथ getStaticPaths के बारे में यह बात है। यह काम करता है। तकनीकी रूप से। लेकिन आपका बिल्ड टाइम आपको अपनी जीवन के चुनावों पर सवाल उठाने पर मजबूर करेगा।

हमारे पहले पूरे build में 4 घंटे 47 मिनट लगे। Vercel पर। जो, अगर आप अपनी plan limits के साथ सावधानी नहीं रखते, तो यह वह तरह की चीज है जो रात 2 बजे एक billing notification का कारण बनती है। मैंने अपने फोन से उस Slack alert को देखा और सच में WordPress इस्तेमाल करने पर विचार किया।

`fallback: 'blocking'` Trap

मेरी पहली सोच सब कुछ pre-render करने की थी। हर पेज, हर combination। बुरा आइडिया, और उस वजह से नहीं जो ज्यादातर tutorials warn करते हैं (जो आमतौर पर बस "इसमें समय लगता है")। असली समस्या cache invalidation है। जब कोई hosting provider अपनी pricing update करता है (और वे करते हैं, लगातार), तुम्हें affected pages को rebuild करना पड़ता है। अगर सब कुछ statically pre-rendered है ISR के बिना, तो तुम उन data changes के लिए full rebuilds trigger कर रहे हो जो 25,000 में से शायद 30 पेजों को impact करते हैं।

मैं Incremental Static Regeneration पर चला गया, ज्यादातर पेजों के लिए 86400 सेकंड (24 घंटे) का revalidate और कीमत-भारी प्रदाता पेजों के लिए 3600 सेकंड। यह पूरे प्रोजेक्ट में सबसे बड़ा क्वालिटी-ऑफ-लाइफ सुधार था। बिल्ड टाइम 40 मिनट से भी कम हो गया क्योंकि हम सिर्फ ट्रैफिक प्राथमिकता के आधार पर शीर्ष ~2,000 पेजों को पहले से रेंडर कर रहे थे और बाकी को fallback: 'blocking' के साथ ऑन-डिमांड जनरेट होने देते थे।

Splitting the Route Tree

एक चीज जो मैं अलग तरीके से करूंगा, और जो मैं अभी हर डेव को Seahawk में बताता हूं जो किसी बड़े प्रोग्रामेटिक प्रोजेक्ट पर काम करता है: जल्दी अपनी राउट ट्री को स्प्लिट करो। एक monolithic getStaticPaths फंक्शन न रखो जो 25,000 slugs रिटर्न करने की कोशिश कर रहा हो। हमने अपने को इनमें तोड़ा:

  1. /providers/[slug], individual provider pages (~400)
  2. /compare/[slugA]-vs-[slugB], head-to-head comparison pages (~8,000)
  3. /category/[type], category landing pages (~40)
  4. /location/[country]/[type], geo × category combinations (~16,000+)
  5. /best/[use-case], curated list pages (~600)

हर route group का अपना revalidation cadence है, अपना data-fetching logic है, और यह काफ़ी महत्वपूर्ण है, अपनी build priority है। location pages लगभग पूरी तरह on-demand हैं। provider pages हमेशा pre-rendered होते हैं। साफ़-सुथरा अलगाव।

---

The Data Pipeline Mess (And How We Fixed It)

2023 की शुरुआत में मैंने HostList के data collection side को बहुत loosely build करने की गलती की। हमारे पास एक scraping script था (Python में लिखा, BeautifulSoup और Webshare से rotating proxy pool का इस्तेमाल करते हुए), सुधार के लिए एक manual Google Sheet, और एक Supabase table। सत्य के तीन स्रोत। कोई भी एक-दूसरे से सही तरीके से बात नहीं कर रहा था।

एक junior developer, अच्छा बच्चा, अभी bootcamp से निकला, तीन हफ्ते एक sync script को maintain करता रहा जो Sheet और Supabase के बीच काम कर रहा था और हर बार जब कोई column का नाम बदलता था तो टूट जाता था। मुझे पहले ही हफ्ते Sheet को खत्म कर देना चाहिए था और एक proper internal admin UI build करना चाहिए था। हमने आखिरकार किया, Next.js API routes और एक Retool dashboard का इस्तेमाल करके, लेकिन हमने probably 60 engineering hours बर्बाद कर दिए।

समाधान: एक ही सत्य का स्रोत, हमेशा। डेटाबेस आधिकारिक है। हर चीज़ डेटाबेस में लिखी जाती है। एडमिन यूआई डेटाबेस से पढ़ता और लिखता है। स्क्रेपर डेटाबेस में लिखता है। यह स्पष्ट लगता है। पीछे मुड़कर देखने पर हमेशा ऐसा ही लगता है।

डेटा को बड़े पैमाने पर ताज़ा रखना

इसी साइज़ की डायरेक्टरी के लिए, डेटा ताज़ापन SEO से उतना ही चिंता का विषय है जितना UX का। Google देखता है जब प्राइसिंग टेबल £2.99/month दिखाता है किसी ऐसी योजना के लिए जो आठ महीने से £5.99 की है। हमने सेट अप किया:

  • Railway cron पर चलने वाली साप्ताहिक स्क्रेप जॉब (सस्ता, भरोसेमंद, डेडिकेटेड सर्वर की ज़रूरत नहीं)
  • एक Supabase डेटाबेस webhook जो तब फायर होता है जब price_updated_at कॉलम बदलता है, एक Next.js revalidation एंडपॉइंट को हिट करता है
  • Retool में मैनुअल ओवरराइड फ़्लैग ~30 प्रोवाइडर्स के लिए जिनकी साइट्स सक्रिय रूप से स्क्रेपर्स को ब्लॉक करती हैं

वह revalidation endpoint, /api/revalidate?secret=TOKEN&path=/providers/siteground, एक standard Next.js feature है, लेकिन इसे database webhook से जोड़ने में कुछ plumbing करनी पड़ी। हर minute की कीमत थी।

---

SEO आर्किटेक्चर: असली में क्या काम आया

मैंने पर्याप्त कंटेंट साइटें बनाई हैं यह जानने के लिए कि 25,000 पेज होना 25,000 पेज होने जैसा नहीं है जो रैंक करते हैं। तुलना पेज जाल थे। हमने अपने ~400 प्रदाताओं के लिए हर संभावित A-vs-B संयोजन तैयार किया, जिससे हमें लगभग 79,800 सैद्धांतिक जोड़े मिले। हमने उनमें से ~8,000 बनाए। और उनमें से अधिकांश, ईमानदारी से कहूं तो, पतले थे।

ईमानदारी से कहूँ तो: मैं लालची हो गया। SEO logic सही था, "SiteGround vs Bluehost" को real search volume मिलती है, comparison queries की long-tail बहुत बड़ी है, लेकिन हमने हर एक page को justify करने के लिए काफी unique content नहीं बनाया। Google ने comparison section को crawl करना शुरू किया और clearly फैसला किया कि इसका वक्त बर्बाद करने लायक नहीं। Google की अपनी guidance thin content के बारे में बिल्कुल साफ है, और मुझे पहले ही खुद से ज्यादा सख्त होना चाहिए था।

हमने क्या किया रिकवरी के लिए

हमने साफ किया। Comparison pages को ~8,000 से घटाकर ~1,200 रखा, सिर्फ वे pairs जिनके पास demonstrable search volume है (Ahrefs में verified, कम से कम 50 monthly searches globally)। फिर हमने बाकी बचे pages को enrich किया:

  • डायनामिक "किसके लिए सर्वश्रेष्ठ है" सेक्शन संरचित प्रदाता डेटा से खींचे गए
  • असली अपटाइम डेटा (हमने एक तीसरे पक्ष के अपटाइम API के साथ एकीकृत किया)
  • Trustpilot डेटा से सीड किए गए उपयोगकर्ता समीक्षा सारांश, जहां उपलब्ध हो

परिणाम 1,200 पेज थे जो वास्तव में उपयोगी थे बजाय 8,000 पेजों के जो नहीं थे। तुलना सेक्शन पर ऑर्गेनिक ट्रैफिक अगले तीन महीनों में 340% बढ़ा। प्रतिकूल लगता है जब तक कि ऐसा न हो।

इस स्केल पर इंटरनल लिंकिंग

25,000 pages के साथ, internal linking manual नहीं हो सकती। हमने एक related-pages component build किया जो Supabase को build time पर query करता है (getStaticProps में) और category और location overlap के आधार पर पाँच सबसे relevant adjacent pages return करता है। कोई editorial intervention की जरूरत नहीं। यह perfect नहीं है, कभी-कभी एक VPS hosting page किसी थोड़े sideways चीज़ को link करता है, लेकिन यह 90% सही है, और इसका मतलब था कि हर page के पास day one से contextually relevant internal links थे।

---

परफॉर्मेंस: वह हिस्सा जो आपको विनम्र करता है

आप सोचेंगे कि static generation performance को आसान बना देगा। और conceptual level पर, यह करता है, pre-rendered HTML, edge-cached Vercel के CDN पर, कोई server-rendering overhead नहीं। लेकिन 25,000 pages का मतलब 25,000 मौके हैं कि आप अपने component tree के बारे में गलत निर्णय ले सकते हैं।

हमारी biggest performance problem provider comparison table था। यह एक heavy client-side React component था, बहुत सारे state, बहुत सारे conditional rendering, दोनों provider pages और comparison pages पर use होता था। Mobile पर, यह around 4.8 seconds की Largest Contentful Paint cause कर रहा था। बुरा। Really बुरा एक site के लिए जहाँ primary traffic वे लोग हैं जो किसी खरीद के फैसले के बीच में हैं।

हमने इसे एक server-rendered static table के रूप में rebuild किया जिसमें interactive filter bits के लिए एक thin React hydration layer था। LCP 1.9 seconds तक गिर गया। यह magic नहीं है, यह सिर्फ boring चीज़ को properly करना है।

इमेज समस्या

हर provider के पास एक logo है। 400 logos, साथ ही screenshots, UI previews, feature icons। हमने पहले दो महीनों के लिए Vercel के built-in image optimisation पर ये सब host करने की गलती की। Bandwidth costs quietly भयानक थे। सब कुछ Cloudflare R2 पर move किया एक custom domain के साथ, अपना Vercel bill £180/month से £40/month तक drop किया। अगर आप कोई image-heavy चीज़ build कर रहे हैं, तो Cloudflare R2 को जल्दी देखें, free egress genuinely scale पर useful है।

---

बिल्ड पाइपलाइन असली में अब कैसी दिखती है

किसी के लिए भी जो ठोस तस्वीर चाहता है:

  1. Data collection, Python scraper एक Railway cron job पर, Supabase Postgres में लिखता है।
  2. एडमिन लेयर, Retool डैशबोर्ड मैनुअल एडिट्स, करेक्शन्स, और प्रोवाइडर फ्लैग्स के लिए
  3. Next.js ऐप, Pages router (हमने शुरुआत की थी App Router के स्टेबल होने से पहले), Vercel पर डिप्लॉय किया हुआ
  4. ISR + on-demand रीवैलिडेशन, टॉप ~2,000 पेज प्री-बिल्ट, बाकी on-demand, सभी के साथ 24h रीवैलिडेशन
  5. इमेजेस, Cloudflare R2, एक कस्टम सबडोमेन से सर्व की जाती हैं Cloudflare CDN के साथ फ्रंट में
  6. एनालिटिक्स, Plausible प्राइवेसी-फ्रेंडली ट्रैफिक डेटा के लिए, Ahrefs रैंकिंग ट्रैकिंग के लिए
  7. अपटाइम मॉनिटरिंग, BetterUptime पांच सबसे ट्रैफिक-हेवी पेज टाइप्स को देखता हुआ

यह glamorous नहीं है। यह maintain करने के लिए ज्यादातर बोरिंग भी है, जो कि बिल्कुल वही है जो आप चाहते हैं infrastructure से जिसे आप तीन साल तक चलाने वाले हैं।

---

ईमानदारी से की गई गलतियाँ, क्रमांकित

  1. बहुत व्यापक शुरुआत की। 25,000 पेज हमेशा लक्ष्य था, लेकिन मुझे 500 हाई-क्वॉलिटी पेज के साथ लॉन्च करना चाहिए था और विस्तार करना चाहिए था। इसके बजाय मैंने सब कुछ के साथ लॉन्च किया और पहले चार महीनों के लिए Google क्रॉल बजट की समस्या हुई।
  2. दिन एक से रीवेलिडेशन ठीक से सेटअप नहीं किया। हमने दो महीने पूरी रिबिल्ड्स पर बर्बाद किए जो ISR ने अनावश्यक बना दिया होता।
  3. Google शीट रखी। सिंगल सोर्स ऑफ ट्रुथ पहले सप्ताह से ही नॉन-नेगोशिएबल होना चाहिए था।
  4. कम्पेरिजन पेज क्वॉलिटी को कम आंका। वॉल्यूम एक स्ट्रेटेजी नहीं है।
  5. Vercel के image optimisation का इस्तेमाल बहुत लंबे समय तक किया। R2 पर छह हफ्ते बाद शिफ्ट किया, जितना पहले करना चाहिए था।
  6. रूट ट्री को काफी जल्दी स्प्लिट नहीं किया। तेज़ और धीमे रूट को एक ही getStaticPaths कॉल में मिक्स किया और फिर आश्चर्य हुआ कि बिल्ड्स धीमे क्यों हैं।

इनमें से हर एक चीज एक फैसला है जो उस वक्त रीजनेबल लगा था। यही वह हिस्सा है जो ट्यूटोरियल्स कैप्चर नहीं करते, बुरे आर्किटेक्चरल डिसीजन्स आमतौर पर अच्छे-सुनने वाले जस्टिफिकेशन्स होते हैं जब आप उन्हें लेते हो।

---

FAQ

शुरुआती बिल्ड को live जाने में कितना समय लगा?

पहली commit से v1 कहलाने लायक version तक सात महीने लगे। पहला rough public version करीब चौथे महीने में live था, लेकिन उसमें गंभीर content की कमी थी और comparison section ज्यादातर बेकार था। मैं कहूँ तो "technically live" होने में चार महीने और फिर "actually good" होने में तीन महीने और लगे।

क्या आप आज शुरुआत करते तो App Router use करते?

शायद हाँ, अगर नई projects 2023 के late से शुरू हों। App Router के server components इस तरह के data-heavy page generation के लिए बिल्कुल सटीक होते। लेकिन existing 25,000-page Pages Router app को migrate करना ऐसा project नहीं है जो मैं जल्द ही शुरू करूँ। Pages Router अभी भी काम करता है, और "काम करता है" को कम आँका जाता है।

जब providers business से बाहर निकल जाएँ या अपनी offering को significantly बदलें, तो आप कैसे handle करते हैं?

हमारे पास डेटाबेस में एक स्टेटस फ्लैग है, active, deprecated, redirected। Deprecated प्रोवाइडर्स को एक स्लिम आर्काइव पेज मिलता है पूरी तरह रिमूवल के बजाय, जो किसी भी बैकलिंक्स को प्रीजर्व करता है। Redirected प्रोवाइडर्स (जैसे जब एक होस्ट दूसरे को एक्वायर करता है) को 301 मिलता है जो Next.js redirects कॉन्फिग के जरिए हैंडल किया जाता है next.config.js में। हम स्टेटस फ्लैग्स को मंथली रिव्यू करते हैं।

अगर आप यह फिर से करते तो Next.js की जगह क्या use करते?

मुझे सचमुच नहीं पता। Astro mostly-static content sites के लिए दिलचस्प है, और मैं इसे एक छोटी project पर try कर रहा हूँ। लेकिन Next.js ने हमें flexibility दी कि एक ही codebase में static और dynamic दोनों sections हो सकें, जो मायने रखता था। किसी purely static directory के लिए जिसमें कोई interactive features न हों, Astro build करने में तेजी और चलाने में कम खर्च दे सकता है। एक साल बाद फिर से पूछना।

आप scrapers को पूरी directory copy करने से कैसे रोकते हैं?

सच कहूँ तो? आप पूरी तरह नहीं रोक सकते। हम API routes को rate-limit करते हैं, Cloudflare के bot management का frontend पर use करते हैं, और structured data को rotate करते हैं ताकि scraped copies जल्दी stale हो जाएँ। लेकिन अगर कोई public-facing directory को clone करना चाहे, तो वह कोई न कोई रास्ता ढूँढ ही लेगा। Moat data freshness और UX quality की है, technical obfuscation की नहीं।

---

समापन विचार

HostList कोई रनअवे सक्सेस नहीं है। यह पैसा बनाता है, एफिलिएट कमीशन्स, कुछ डायरेक्ट एडवर्टाइजिंग डील्स, और यह रीजनेबली वेल रैंक करता है शायद 600 टर्म्स के लिए जो मैंने ओरिजिनली टार्गेट किए थे। वह ठीक है। यह एक लर्निंग प्रोजेक्ट था जो होता है रेवेन्यू भी जेनरेट करता है, जो सबसे अच्छी किस्म है।

अगर आप Next.js पर एक बड़े पैमाने की programmatic SEO साइट बनाने के बारे में सोच रहे हैं, तो मेरी ईमानदारी से सलाह यह है: करो। यह job के लिए genuinely एक अच्छा stack है। लेकिन उतना कम बनाओ जितना तुम्हें लगता है कि तुम्हें ज़रूरत है, इसे उतना बेहतर बनाओ जितना तुम्हें लगता है कि तुम्हारे पास समय है, और एक भी page template लिखने से पहले अपनी data architecture को सही कर लो।

Tech सबसे आसान हिस्सा है। हमेशा ही होता है।

< BACK