how-i-built-25000-page-directory-nextjs.html
< BACK मंद रोशनी वाला सर्वर रूम कॉरिडोर गर्म एम्बर लाइट और स्टील रैक के साथ जो मुलायम फोकस में गायब हो जाते हैं

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

2022 के अंत में कहीं मैंने खुद को समझा लिया कि एक वेब होस्टिंग डायरेक्टरी बनाना सीधा काम होगा। डेटा जमा करो, पेज जनरेट करो, रैंक करो, मुद्रीकृत करो। साफ-सुथरा। मैंने पहले से ही प्रोग्रामेटिक SEO प्रोजेक्ट किए थे — एक यूके क्लाइंट के लिए एक स्थानीय रियल एस्टेट टूल, एक SaaS तुलना साइट जो 40k मासिक विज़िट पर पहुँची — तो मैंने सोचा HostList छः हफ्ते का प्रोजेक्ट होगा। यह सात महीने के करीब निकला। और इसने कुछ चीजों को लगभग तोड़ दिया: मेरी नींद की शेड्यूल, मेरे एक जूनियर डेवलपर का आत्मविश्वास, और एक £180/महीने का Vercel बिल जिसके लिए मैंने बजट नहीं रखा था।

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

---

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

HostList को सरल होना चाहिए था। वेब होस्टिंग प्रदाताओं की एक डायरेक्टरी — शेयर्ड, VPS, डेडिकेटेड, मैनेज्ड WordPress — हर प्रदाता के लिए अलग पेज, तुलना पेज, कैटेगरी पेज, और स्थान-आधारित पेज (जैसे "जर्मनी में सर्वश्रेष्ठ होस्टिंग")। गणित चलाओ: ~400 प्रदाता × कई पेज टाइप × 20+ फिल्टर संयोजन। आप 25,000 पेज तक सोचने से जल्दी पहुँच जाते हैं।

मैंने Next.js को लगभग बिना सोचे-समझे चुन लिया। हम Seahawk में अपने ज्यादातर बड़े React-based builds के लिए इसका इस्तेमाल करते हैं। यह ecosystem परिपक्व है, getStaticProps और getStaticPaths SEO-heavy static generation के लिए समझदारी भरा विकल्प हैं, और व्यक्तिगत रूप से मुझे file-based routing इस स्केल पर Remix या Gatsby से ज्यादा समझने में आसान लगता है।Next.jsalmost without thinking about it. We use it at Seahawk for most of our bigger React-based builds. The ecosystem is mature,getStaticPropsandgetStaticPathsmake sense for SEO-heavy static generation, and I personally find the file-based routing easier to reason about than Remix or Gatsby at this scale.

पहला असली फैसला data layer को लेकर था। मैंने headless CMS को तेजी से खारिज कर दिया — मुझे 25,000 entries के लिए Contentful की दरें देने का मन नहीं था, और मैं किसी CMS पर bulk programmatic writes को साफ-सुथरे तरीके से संभालने के लिए भरोसा नहीं कर सकता था। हम Supabase पर एक Postgres database पर उतरे, एक lightweight Next.js API layer के साथ जो इसके सामने बैठा है। वह हिस्सा असल में ठीक काम किया। बाकी सब कुछ ही जटिल हो गया।Supabase, with a lightweight Next.js API layer sitting in front of it. That part actually worked fine. It's almost everything else that got complicated.

---

Static Generation at Scale: What Nobody Warns You About

देखिए, 25,000 routes के साथ getStaticPaths के बारे में यह बात है कि यह काम करता है। तकनीकी तौर पर। लेकिन आपकी build times आपको अपनी जीवन के विकल्पों पर सवाल उठाने पर मजबूर करेंगे।getStaticPathswith 25,000 routes. It works. Technically. But your build times will make you question your life choices.

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

`fallback: 'blocking'` Trap

मेरी शुरुआती सोच सब कुछ pre-render करने की थी। हर page, हर combination। बुरा विचार — और उस वजह से नहीं जो ज्यादातर tutorials आपको चेतावनी देते हैं (जो आमतौर पर सिर्फ "इसमें समय लगता है")। असली समस्या cache invalidation है। जब एक hosting provider अपनी pricing अपडेट करता है (और वे करते हैं, लगातार), आपको प्रभावित pages को rebuild करने की जरूरत होती है। अगर सब कुछ statically pre-rendered है बिना ISR के, तो आप ऐसे data changes के लिए full rebuilds को trigger कर रहे हैं जो शायद 25,000 में से 30 pages को प्रभावित करते हैं।cache invalidation. When a hosting provider updates their pricing (and they do, constantly), you need to rebuild affected pages. If everything is statically pre-rendered with no ISR, you're triggering full rebuilds for data changes that affect maybe 30 pages out of 25,000.

मैं Incremental Static Regeneration पर स्विच किया एक revalidate के साथ 86400 seconds (24 घंटे) के लिए ज्यादातर pages के लिए, और 3600 seconds pricing-heavy provider pages के लिए। यह पूरे project में एकमात्र सबसे बड़ा quality-of-life improvement था। Build times 40 मिनट से नीचे चली गए क्योंकि हम सिर्फ top ~2,000 pages को traffic priority के आधार पर pre-render कर रहे थे और बाकी को on-demand generate करने दे रहे थे fallback: 'blocking' के साथ।Incremental Static Regenerationwith arevalidateof 86400 seconds (24 hours) for most pages, and 3600 seconds for pricing-heavy provider pages. This was the single biggest quality-of-life improvement in the entire project. Build times dropped to under 40 minutes because we were only pre-rendering the top ~2,000 pages by traffic priority and letting the rest generate on-demand withfallback: 'blocking'.

Splitting the Route Tree

एक चीज़ जो मैं अलग तरीके से करता, और अब Seahawk में हर dev को बताता हूँ जो एक बड़े programmatic प्रोजेक्ट पर काम करे: अपने route tree को जल्दी split करो। एक monolithic getStaticPaths फंक्शन न रखो जो 25,000 slugs return करने की कोशिश करे। हमने अपने को इन हिस्सों में तोड़ा:getStaticPathsfunction trying to return 25,000 slugs. We broke ours into:

  1. /providers/[slug] — individual provider pages (~400)— individual provider pages (~400)
  2. /compare/[slugA]-vs-[slugB] — head-to-head comparison pages (~8,000)— head-to-head comparison pages (~8,000)
  3. /category/[type] — category landing pages (~40)— category landing pages (~40)
  4. /location/[country]/[type] — geo × category combinations (~16,000+)— geo × category combinations (~16,000+)
  5. /best/[use-case] — curated list pages (~600)— 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। सत्य के तीन स्रोत। कोई भी एक-दूसरे से सही तरीके से बात नहीं कर रहा था।

एक जूनियर डेव — अच्छा लड़का, बूटकैम्प से ही निकला था — ने तीन हफ़्ते एक सिंक स्क्रिप्ट को मेंटेन किया जो Sheet और Supabase के बीच चलती थी और हर बार जब कोई कॉलम नाम बदलता था तो टूट जाती थी। मुझे पहले हफ़्ते ही Sheet को खत्म कर देना चाहिए था और एक सही इंटरनल एडमिन UI बना देना चाहिए था। आखिरकार हमने किया, Next.js API routes और एक Retool डैशबोर्ड लगाकर, लेकिन हमने शायद 60 इंजीनियरिंग घंटे उस तक पहुँचने में बर्बाद कर दिए।

समाधान: हमेशा एक ही सच्चाई का स्रोत। डेटाबेस ही मालिक है। सब कुछ डेटाबेस में लिखता है। एडमिन UI डेटाबेस से पढ़ता है और डेटाबेस में लिखता है। स्क्रेपर डेटाबेस में लिखता है। लगता है ज़ाहिर है। हमेशा ही है, पीछे मुड़कर देखने पर।one source of truth, always. The database is canonical. Everything writes to the database. The admin UI reads from and writes to the database. The scraper writes to the database. Sounds obvious. It always does, in hindsight.

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

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

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

वह revalidation endpoint — /api/revalidate?secret=TOKEN&path=/providers/siteground — एक स्टॉक Next.js फीचर है, लेकिन इसे डेटाबेस webhook से जोड़ने में कुछ प्लंबिंग लगी। हर मिनट लायक़।/api/revalidate?secret=TOKEN&path=/providers/siteground— is a stock Next.js feature, but wiring it to a database webhook took a bit of plumbing. Worth every minute.

---

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

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

ईमानदार स्वीकारोक्ति: मैं लालची हो गया। SEO लॉजिक सही थी — "SiteGround vs Bluehost" को असली सर्च वॉल्यूम मिलता है, तुलना प्रश्नों की लॉन्ग-टेल विशाल है — लेकिन हमने हर एक पेज के अस्तित्व को सही ठहराने के लिए पर्याप्त यूनिक कंटेंट नहीं बनाया। Google ने तुलना सेक्शन को क्रॉल करना शुरू किया और स्पष्ट रूप से तय किया कि यह उसके समय के लायक नहीं है। पतले कंटेंट पर Google का अपना निर्देश इस बारे में सीधा है, और मुझे पहले ही खुद के साथ अधिक सीधा होना चाहिए था।Google's own guidance on thin contentis blunt about this, and I should have been blunter with myself earlier.

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

हमने काटा। तुलना पेजों को ~8,000 से घटाकर ~1,200 तक — केवल वे जोड़े जिनमें प्रदर्शनीय सर्च वॉल्यूम है (Ahrefs में सत्यापित, न्यूनतम 50 मासिक सर्च विश्वव्यापी)। फिर हमने शेष पेजों को निम्नलिखित से समृद्ध किया:

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

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

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

25,000 पेजों के साथ, इंटरनल लिंकिंग मैनुअल नहीं हो सकती। हमने एक संबंधित-पेज कंपोनेंट बनाया जो बिल्ड टाइम पर (getStaticProps में) Supabase को क्वेरी करता है और श्रेणी और लोकेशन ओवरलैप के आधार पर पाँच सबसे प्रासंगिक आसन्न पेज लौटाता है। कोई संपादकीय हस्तक्षेप नहीं। यह परफेक्ट नहीं है — कभी-कभी एक VPS होस्टिंग पेज कुछ अजीब चीज़ से लिंक करता है — लेकिन यह 90% सही है, और इसका मतलब था कि हर पेज के पास दिन एक से ही संदर्भ-प्रासंगिक इंटरनल लिंक थे।getStaticProps) and returns the five most relevant adjacent pages based on category and location overlap. No editorial intervention needed. It's not perfect — occasionally a VPS hosting page links to something a bit sideways — but it's 90% right, and it meant every page had contextually relevant internal links from day one.

---

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

आप सोचेंगे कि स्टैटिक जनरेशन परफॉर्मेंस को आसान बना देगी। और वैचारिक स्तर पर, यह करता है — पूर्व-रेंडर किया गया HTML, Vercel के CDN पर edge-कैश्ड, कोई सर्वर-रेंडरिंग ओवरहेड नहीं। लेकिन 25,000 पृष्ठ मतलब 25,000 मौके हैं कि आपने अपने कंपोनेंट ट्री के बारे में कोई बुरा फैसला किया हो।

हमारी सबसे बड़ी परफॉर्मेंस समस्या प्रोवाइडर तुलना तालिका थी। यह एक भारी क्लाइंट-साइड React कंपोनेंट था — बहुत सारी स्टेट, बहुत सारी कंडिशनल रेंडरिंग, प्रोवाइडर पृष्ठों और तुलना पृष्ठों दोनों पर उपयोग किया गया। मोबाइल पर, यह लगभग 4.8 सेकंड का Largest Contentful Paint पैदा कर रहा था। बुरा। एक साइट के लिए वाकई बुरा जहां प्राथमिक ट्रैफिक खरीद पर फैसले के बीच में लोग हैं।Largest Contentful Paintof around 4.8 seconds. Bad. Really bad for a site where the primary traffic is people mid-decision on a purchase.

हमने इसे सर्वर-रेंडर किए गए स्टैटिक टेबल के रूप में फिर से बनाया, जिसमें इंटरेक्टिव फिल्टर बिट्स के लिए एक पतली React हाइड्रेशन लेयर थी। LCP 1.9 सेकंड तक गिरा। यह जादू नहीं है — यह बस सही तरीके से बोरिंग चीज़ करना है।

इमेज समस्या

हर प्रोवाइडर के पास एक लोगो है। 400 लोगो, साथ ही स्क्रीनशॉट, UI प्रिव्यू, फीचर आइकॉन। हमने पहले दो महीनों के लिए Vercel के बिल्ट-इन इमेज ऑप्टिमाइज़ेशन पर इन्हें होस्ट करने की गलती की। बैंडविड्थ लागत चुप्पी से भयानक थी। सब कुछ Cloudflare R2 में एक कस्टम डोमेन के साथ स्थानांतरित किया, हमारा Vercel बिल £180/माह से £40/माह तक गिरा। अगर आप कुछ इमेज-हेवी बना रहे हैं, तो Cloudflare R2 को जल्दी देखें — फ्री एग्रेस स्केल पर वाकई उपयोगी है।Cloudflare R2early — the free egress is genuinely useful at scale.

---

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

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

  1. डेटा संग्रह — Python scraper Railway cron job पर चलता है, Supabase Postgres में लिखता है— Python scraper on a Railway cron job, writes to Supabase Postgres
  2. Admin layer — Retool dashboard मैनुअल edits, corrections, और provider flags के लिए— Retool dashboard for manual edits, corrections, and provider flags
  3. Next.js app — Pages router (हमने App Router के पर्याप्त स्थिर होने से पहले शुरुआत की थी), Vercel पर deployed— Pages router (we started before App Router was stable enough to trust), deployed on Vercel
  4. ISR + on-demand revalidation — शीर्ष ~2,000 pages pre-built, बाकी on-demand, सभी 24h revalidation के साथ— top ~2,000 pages pre-built, rest on-demand, all with 24h revalidation
  5. Images — Cloudflare R2, custom subdomain के जरिए serve किए जाते हैं Cloudflare CDN के साथ— Cloudflare R2, served via a custom subdomain with Cloudflare CDN in front
  6. Analytics — Plausible privacy-friendly traffic data के लिए, Ahrefs ranking tracking के लिए— Plausible for privacy-friendly traffic data, Ahrefs for ranking tracking
  7. Uptime monitoring — BetterUptime पाँच सबसे ज्यादा traffic वाले page types को देखता है— BetterUptime watching the five most traffic-heavy page types

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

---

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

  1. शुरुआत बहुत व्यापक थी। 25,000 पेजेस हमेशा ही लक्ष्य था, लेकिन मुझे 500 उच्च-गुणवत्ता वाले पेजेस के साथ लॉन्च करना चाहिए था और फिर विस्तार करना चाहिए था। इसकी जगह मैंने सब कुछ के साथ लॉन्च किया और पहले चार महीने तक Google के crawl budget में समस्या आई।25,000 pages was always the goal, but I should have launched with 500 high-quality pages and expanded. Instead I launched with everything and had a Google crawl budget problem for the first four months.
  2. दिन पहले से ही revalidation सही तरीके से सेट नहीं किया। हमने दो महीने फुल रीबिल्ड में बर्बाद किए जो ISR ने अनावश्यक बना दिए होते।We wasted two months on full rebuilds that ISR would have made unnecessary.
  3. Google Sheet को रखा। Single source of truth पहले हफ्ते से ही गैर-वार्ता-योग्य होना चाहिए था।Single source of truth should have been non-negotiable from week one.
  4. Comparison page की गुणवत्ता को कम आंका। Volume एक रणनीति नहीं है।Volume is not a strategy.
  5. Vercel image optimisation को बहुत लंबे समय तक इस्तेमाल किया। R2 पर छह हफ्ते बाद में shift किया जितना हमें करना चाहिए था।Moved to R2 six weeks later than we should have.
  6. Route tree को जल्दी enough split नहीं किया। Fast और slow routes को एक ही getStaticPaths call में मिलाया और फिर सोचने लगे कि builds slow क्यों हैं।Mixed fast and slow routes in the samegetStaticPathscall and then wondered why builds were slow.

ये हर एक फैसला वह था जो उस समय समझदारी भरा लग रहा था। यह वह हिस्सा है जो tutorials में नहीं आता — बुरे architectural फैसले आमतौर पर अच्छे-सुनने वाले तर्क रखते हैं जब आप उन्हें लेते हैं।

---

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 करते हैं?

Database में हमारे पास एक status flag है — active, deprecated, redirected। Deprecated providers को पूरी तरह हटाने के बजाय एक slim archive page मिलता है, जिससे कोई भी backlinks बचे रहते हैं। Redirected providers (जैसे जब एक host दूसरे को acquire कर ले) को 301 दिया जाता है जो next.config.js में Next.js redirects config के through handle होता है। हम monthly status flags review करते हैं।statusflag in the database —active,deprecated,redirected. Deprecated providers get a slim archive page rather than a full removal, which preserves any backlinks. Redirected providers (e.g. when one host acquires another) get a 301 handled via the Next.jsredirectsconfig innext.config.js. We review the status flags monthly.

अगर आप यह फिर से करते तो 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 कोई बेतहाशा सफलता नहीं है। यह पैसा कमाता है — affiliate commissions, कुछ direct advertising deals — और मूल रूप से जिन 600 शब्दों को मैंने target किया था, उनके लिए यह काफी अच्छी तरह rank करता है। यह ठीक है। यह एक learning project था जो revenue भी generate करता है, जो सबसे अच्छी तरह की चीज़ है।

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

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

< BACK