< BACK Next.js और Supabase के साथ Real-Time नीलामी साइट बनाना -- लाइन-आर्ट चित्रण

Next.js और Supabase के साथ रीयल-टाइम नीलाम साइट बनाना

2021 की एक गुरुवार की दोपहर को एक क्लाइंट ने मुझे कॉल किया, बाथ में एक एंटीक्स डीलर था, वह अपनी मासिक व्यक्तिगत नीलामियों को ऑनलाइन ले जाना चाहता था। काफी सरल लगा। फिर उसने कहा "और बोलियों को सभी दर्शकों के लिए लाइव अपडेट करना होगा, कोई पेज रीफ्रेश नहीं।" ठीक है। तब एक "साधारण WordPress जॉब" दो सप्ताह की आर्किटेक्चर बातचीत में बदल गया।

मुख्य बात: Next.js और Supabase पर लाइव बिडिंग रीयलटाइम चैनलों, रो-लेवल सिक्योरिटी और सर्वर-साइड बिड वेलिडेशन पर निर्भर करती है; UI से पहले स्टेट मशीन को सही तरीके से सेट करें।

मैंने Seahawk Media पर 12,000 से अधिक साइटें बनाई हैं, और रीयल-टाइम फीचर्स वे होते हैं जो आपको काट लेते हैं अगर आप शुरुआत से ही उन्हें सही तरीके से प्लान नहीं करते। हर पाँच सेकंड में पोलिंग ठीक लगती है जब तक 200 बोलीकर्ता एक ही समापन बिंदु को एक साथ हथौड़ा नहीं मार रहे हों और आपका होस्टिंग बिल रातोंरात दोगुना नहीं हो जाता। तो मैं आपको दिखाता हूँ कि आज मैं एक उचित लाइव नीलामी प्लेटफॉर्म कैसे बनाऊँगा, Next.js और Supabase का उपयोग करके, जो मैंने वास्तव में शिप किया है।

---

इसके लिए विशेष रूप से Next.js और Supabase क्यों

देखिए, रीयल-टाइम करने के एक दर्जन तरीके हैं। Node सर्वर पर Socket.io, Ably, Pusher, Firebase, मैंने सभी का विभिन्न बिंदुओं पर उपयोग किया है। लेकिन Next.js + Supabase संयोजन यहाँ अपनी जगह बनाता है एक विशेष कारण के लिए: Supabase Realtime PostgreSQL के लॉजिकल रेप्लिकेशन के शीर्ष पर बनाया गया है, जिसका मतलब है कि आपके लाइव बिड अपडेट और आपकी स्थायी डेटा परत एक ही सिस्टम हैं। दो सत्य के स्रोतों को सिंक करने की कोई परेशानी नहीं। यह आश्चर्य नहीं कि क्या कोई बिड जो WebSocket में गई वह डेटाबेस में भी बनी रही।

Supabase आपको Auth, Row Level Security, और Storage सभी बॉक्स में मिलते हैं। एक नीलामी साइट के लिए, जहां "केवल नीलामी मालिक ही लॉट समाप्त कर सकता है" और "एक यूजर अपनी खुद की वस्तु पर बोली नहीं लगा सकता" असली बिज़नेस नियम हैं, Postgres में RLS policies बिल्कुल सही टूल हैं।

और Next.js क्योंकि, ईमानदारी से कहूँ तो, App Router Server Components के साथ मतलब है कि आप नीलामी कैटलॉग को स्टेटिकली रेंडर कर सकते हैं, SEO को खुश रख सकते हैं, और केवल रीयल-टाइम बिडिंग विजेट को क्लाइंट पर हाइड्रेट कर सकते हैं। वह विभाजन मायने रखता है। आप डायनामिक रेंडरिंग के लिए भुगतान नहीं करना चाहते एक ऐसे पेज पर जो 90% स्टेटिक कंटेंट है।

---

पहले स्कीमा डिज़ाइन करना (इसे स्किप न करें)

यह वह जगह है जहां ज्यादातर लोग जल्दबाजी करते हैं और बाद में इसका पछतावा करते हैं। मैंने Bath एंटीक क्लाइंट के स्कीमा को प्रोजेक्ट के बीच रीफैक्टर करने में शर्मनाक तीन दिन बिताए क्योंकि मैंने bid history मॉडल के बारे में सही तरीके से नहीं सोचा था।

यहाँ मूल संरचना है जो मैं अब उपयोग करता हूं:

  • `profiles`, Supabase के auth.users को विस्तारित करता है, डिस्प्ले नाम, सत्यापित बोलीकर्ता फ्लैग, और एक credit_balance संग्रहीत करता है यदि आप डिपोज़िट-आधारित बिडिंग कर रहे हों।
  • `auctions`, घटना स्वयं; starts_at, ends_at, status(draft | live | closed), और created_by।
  • `lots`, एक नीलामी के भीतर व्यक्तिगत वस्तुएँ; reserve_price, current_bid, current_bidder_id, lot_number, ends_at (लॉट के व्यक्तिगत काउंटडाउन हो सकते हैं)।
  • `bids`, अपरिवर्तनीय केवल-संलग्न लॉग; lot_id, bidder_id, amount, placed_at। इस तालिका को कभी अपडेट न करें। कभी नहीं।
  • `auction_participants`, एक जॉइन तालिका जो ट्रैक करती है कि किसने किस नीलामी के लिए रजिस्टर किया (डिपोज़िट होल्ड और सूचना लक्ष्यीकरण के लिए उपयोगी)।

lots पर current_bid और current_bidder_id columns को intentionally denormalised किया गया है। हाँ, आप उन्हें हर read पर bids table से derive कर सकते हैं, लेकिन concurrent load के तहत वो query expensive हो जाती है जल्दी। इसे denormalise करें, bids table को अपने audit log के रूप में रखें, और एक Postgres function का उपयोग करें lots को atomically update करने के लिए जब एक bid accept किया जाए।

The Atomic Bid Function

यह वह हिस्सा है जिसे अधिकांश ट्यूटोरियल छोड़ देते हैं। नीलामी में race conditions असली समस्या हैं। दो उपयोगकर्ता एक ही मिलीसेकंड में £520 जमा करते हैं, फिर क्या होता है?

जवाब है एक Postgres function जिसमें lot row पर FOR UPDATE locking है:

``` create or replace function place_bid(p_lot_id uuid, p_bidder_id uuid, p_amount numeric) returns json as $$ declare v_lot lots%rowtype; begin select * into v_lot from lots where id = p_lot_id for update;

if v_lot.status!= 'live' then return json_build_object('success', false, 'error', 'Lot is not live'); end if;

if p_amount <= v_lot.current_bid then return json_build_object('success', false, 'error', 'Bid too low'); end if;

if p_bidder_id = v_lot.current_bidder_id then return json_build_object('success', false, 'error', 'You are already the highest bidder'); end if;

insert into bids (lot_id, bidder_id, amount) values (p_lot_id, p_bidder_id, p_amount);

update lots set current_bid = p_amount, current_bidder_id = p_bidder_id where id = p_lot_id;

return json_build_object('success', true, 'new_bid', p_amount); end; $$ language plpgsql security definer; ```

इसे अपने Next.js API route से call करें supabase.rpc('place_bid', {...}) के द्वारा। FOR UPDATE lock का मतलब है केवल एक transaction हर given moment में प्रति lot जीतता है। दूसरा एक serialisation error को पाता है और आप client पर एक friendly "someone just outbid you" message return करते हैं।

---

Row Level Security, नीलामी के नियमों की परत

RLS उन चीजों में से एक है जो developers या तो तुरंत पसंद करते हैं या टालते हैं क्योंकि यह अपारदर्शी लगता है। मैं टालने वाले पक्ष में था जब तक कि Seahawk पर एक fintech प्रोजेक्ट ने मुझे सिखाया कि केवल application code में access control को enforce करना एक गलत तरीके से configured API route दूर है।

एक नीलामी साइट के लिए, ये नीतियां मायने रखती हैं:

  1. कोई भी live lots को पढ़ सकता है, SELECT on lots जहाँ auctions.status = 'live'
  2. केवल प्रमाणित, verified bidders ही bids insert कर सकते हैं, policy में profiles.verified_bidder = true check करें
  3. केवल auction creator ही lot status को update कर सकता है, UPDATE on lots जहाँ auctions.created_by = auth.uid()
  4. Bid history lot के auction creator और bidder स्वयं द्वारा पढ़ी जा सकती है, किसी और को real time में पूरा bid history देखने की जरूरत नहीं

Supabase RLS documentation यहाँ सच में अच्छी है, security definer functions वाले सेक्शन को पढ़ने लायक है, क्योंकि यह place_bid जैसी RPC calls के साथ कैसे interact करता है यह महत्वपूर्ण है।

एक बात का ध्यान रखें: अगर आप अपने Postgres function पर security definer का उपयोग करते हैं (जैसा ऊपर है), तो यह function owner के privileges के साथ चलता है, RLS को bypass करता है। यह जानबूझकर है, आप चाहते हैं कि bid placement bidder के RLS को bypass करे ताकि वह lot row को lock और update कर सके। लेकिन इसका मतलब है कि आपको अपने function के अंदर अपना खुद का business logic checks enforce करना चाहिए, जो ऊपर दिया गया code करता है।

---

Supabase Realtime को Next.js में सेटअप करना

यह वह जगह है जहाँ यह वास्तव में संतोषजनक हो जाता है। Supabase Realtime आपको WebSockets का उपयोग करके Postgres टेबल पर परिवर्तनों की सदस्यता लेने देता है, और क्लाइंट SDK इसे लगभग शर्मनाक रूप से सरल बना देता है।

अपने auction lot page में, एक Client Component Next.js App Router में, आप कुछ इस तरह करेंगे:

``` 'use client'

import { useEffect, useState } from 'react' import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'

export default function LotBidDisplay({ lotId, initialBid }) { const [currentBid, setCurrentBid] = useState(initialBid) const supabase = createClientComponentClient()

useEffect(() => { const channel = supabase.channel(lot-${lotId}).on( 'postgres_changes', { event: 'UPDATE', schema: 'public', table: 'lots', filter:id=eq.${lotId}}, (payload) => { setCurrentBid(payload.new.current_bid) } ).subscribe()

return () => { supabase.removeChannel(channel) }

return <div>Current bid: £{currentBid.toLocaleString()}</div> } ```

एक Server Component से initialBid पास करें जो रिक्वेस्ट टाइम पर ताज़ा डेटा फेच करता है। फिर क्लाइंट ले लेता है, उस विशेष लॉट रो पर UPDATE इवेंट्स को सुनते हुए। हर बार जब place_bid सफलतापूर्वक चलता है, Supabase बदलाव को ब्रॉडकास्ट करता है और हर जुड़े हुए बिडर का UI आम तौर पर 100-300ms के अंदर अपडेट हो जाता है।

Countdown Timer को Handle करना

Lots आमतौर पर एक countdown रखते हैं, "3:42 में बंद होगा"। इसके लिए client clock पर विश्वास न करें। end time को lots.ends_at (Postgres में UTC में stored) से लें और client पर Date.now() का उपयोग करके बाकी सेकंड calculate करें। drift की स्थिति में हर 60 सेकंड में fresh fetch के साथ इसे re-sync करें। और "soft close" logic जोड़ें: अगर कोई bid आखिरी 60 सेकंड में आती है, तो ends_at को दो मिनट के लिए बढ़ाएं। यह standard auction behavior है और bidders इसकी उम्मीद करते हैं।

---

Auction UI के लिए Next.js App Router Architecture

जो page structure मैं use करूँगा:

``app/ auctions/ page.tsx ← Server Component, live auctions list करता है (ISR, revalidate: 60) [auctionId]/ page.tsx ← Server Component, server-side से lots list fetch करता है LotGrid.tsx ← Client Component, lot status changes पर subscribe करता है [lotId]/ page.tsx ← Server Component, initial lot data + SEO के लिए metadata BidPanel.tsx ← Client Component, real-time bid display + bid form``

कैटलॉग (/auctions) 60-सेकंड के रीवेलिडेशन के साथ Incremental Static Regeneration का उपयोग करता है। अलग-अलग lot पेजेस पहली लोड पर सर्वर-साइड रेंडर होते हैं (शेयरिंग, प्रीव्यूइंग, og:image जेनरेशन के लिए), फिर लाइव stuff के लिए क्लाइंट कंपोनेंट्स को हैंड ऑफ करते हैं।

मैं हमेशा एक काम करता हूँ: BidPanel कंपोनेंट को dynamic(() => import('./BidPanel'), { ssr: false }) के पीछे lazy-load रखता हूँ। वैसे भी यह सिर्फ क्लाइंट-साइड पर समझ में आता है, और यह धीमे कनेक्शन वाले यूजर्स के लिए आपका इनिशियल HTML पेलोड पतला रखता है, जो कि अगर आपकी नीलामी का दर्शक बड़ी उम्र का हो (जैसे पुरावस्तु नीलामी आमतौर पर होती है), तो आप सोचते हैं उससे ज़्यादा मायने रखता है।

---

Authentication और "Verified Bidder" Flow

Standard Supabase Auth जिसमें email/password या magic link साइन-अप के लिए ठीक काम करता है। लेकिन auctions को अक्सर एक extra step की जरूरत होती है: bidder verification। आपको क्रेडिट कार्ड hold, ID verification, या बस admin approval की जरूरत हो सकती है कोई कोई भी बोली लगाने से पहले।

जिस पैटर्न का मैं उपयोग करता हूँ: profiles टेबल पर verified_bidder बूलियन, डिफ़ॉल्ट रूप से false। साइन-अप के बाद, उपयोगकर्ता "अपना रजिस्ट्रेशन पूरा करें" स्क्रीन देखता है। एक बार अनुमोदन के बाद (व्यवस्थापक द्वारा मैनुअल रूप से, या Stripe पेमेंट प्राधिकरण के बाद स्वचालित रूप से), आप फ़्लैग को फ्लिप करते हैं। bids पर RLS नीति इसे जांचती है। वे ब्राउज़ कर सकते हैं, देख सकते हैं, लेकिन सत्यापित होने तक बिड नहीं कर सकते।

Stripe पेमेंट ऑथराइजेशन होल्ड के लिए, Stripe के payment intents को capture_method: manual के साथ सही तरीका है — आप £50 होल्ड ऑथराइज करते हैं, जीतने पर उसे कैप्चर करते हैं, नहीं जीतने पर रिलीज़ करते हैं। यह बिना-भुगतान वाली स्थितियों को नाटकीय रूप से कम करता है, जो मेरे विश्वास करें, हर ऑनलाइन नीलामी ऑपरेटर का सबसे बड़ा सिरदर्द है।

---

Deployment, Performance और जो Bits आपको काटेंगे

Vercel पर डिप्लॉय करें, यह Next.js के लिए स्पष्ट पसंद है और एज नेटवर्क Supabase की ग्लोबल इंफ्रास्ट्रक्चर के साथ अच्छी तरह काम करता है। सुनिश्चित करें कि आपका Supabase प्रोजेक्ट उसी AWS रीजन में है जो आपके Vercel डिप्लॉयमेंट रीजन के सबसे करीब है। मैंने 40-60ms का पूरी तरह अनावश्यक लेटेंसी देखा है क्योंकि किसी ने Vercel को us-east-1 में और Supabase को eu-west-2 में डिप्लॉय किया था। एक रीजन चुनें, दोनों को वहाँ रखें।

कुछ चीजें हैं जो आपको दर्द देंगी अगर आप उन्हें शुरुआत से ही संभाल न लें:

  • WebSocket connection limits। Supabase की free tier लगभग 200 concurrent Realtime connections allow करती है। अगर तुम्हारा auction viral हो जाता है, तो यह cap matter करता है। अपनी plan check करो।
  • बोलियों के लिए Optimistic UI। बोली लगाने वाले की स्क्रीन पर सर्वर की पुष्टि से पहले बोली को तुरंत दिखाएं। यदि यह विफल हो जाए (outbid, race condition), एक त्रुटि के साथ वापस लें। 200-300ms सर्वर राउंड-ट्रिप अगोचर है जब तक UI इसका इंतज़ार नहीं करता।
  • नीलाम समाप्ति की अनुमति अवधि। कभी भी एक नीलाम को ends_at पर बिल्कुल बंद न करें। समय सीमा से पहले जमा की गई in-flight बोलियों को संसाधित करने की अनुमति देने के लिए सर्वर-साइड पर 2-3 सेकंड की बफर दें। इसे अपने close_lot शेड्यूल्ड फ़ंक्शन में संभालें।
  • ईमेल नोटिफिकेशन। Supabase Edge Functions को Resend या Postmark के साथ "You've been outbid" और "You won!" ईमेल भेजने के लिए यूज़ करें। अपने Next.js API रूट्स से यह करने की कोशिश न करें, वे टाइम आउट हो सकते हैं, और नीलामी में भाग लेने वाले लोग सच में नाराज़ हो जाते हैं अगर नोटिफिकेशन अविश्वसनीय हों।

---

FAQ

Supabase Realtime कितने concurrent bidders को handle कर सकता है?

Supabase की Pro प्लान डिफ़ॉल्ट रूप से 500 तक की समवर्ती Realtime कनेक्शन को सपोर्ट करती है, और अधिक लिमिट उपलब्ध हैं। अधिकांश नीलामी साइट्स के लिए, जब तक आप Sotheby's की ऑनलाइन नीलामी के आकार की कोई चीज़ नहीं चला रहे हैं, यह काफी़ है। अगर आप हज़ारों समवर्ती दर्शकों की अपेक्षा करते हैं, तो लॉट अपडेट्स को एक एकल सर्वर-साइड चैनल के माध्यम से प्रसारित करने पर विचार करें, प्रति-यूजर सदस्यता के बजाय, और Supabase के Realtime Broadcast फीचर को देखें, जो उच्च fan-out परिदृश्यों के लिए अधिक कुशल है।

क्या मुझे Supabase Realtime का इस्तेमाल करना चाहिए या Ably जैसी dedicated service का?

अधिकांश प्रोजेक्ट्स के लिए, Supabase Realtime पूरी तरह पर्याप्त है और इंटीग्रेशन बहुत सरल है क्योंकि आपका डेटा पहले से Supabase में है। मैं सिर्फ Ably या Pusher के लिए तब जाऊँ अगर आपको ग्लोबली 50ms से कम लेटेंसी चाहिए, या अगर आप लाखों समवर्ती कनेक्शन के साथ कोई चीज़ बना रहे हैं। एक पुरावस्तु नीलामी, एक चैरिटी फंडरेज़र, एक छोटी कला गैलरी की ऑनलाइन नीलामी — Supabase ये सब बिना परेशानी के संभालता है।

अगर यूजर का WebSocket कनेक्शन नीलाम के बीच में ड्रॉप हो जाए तो क्या होता है?

Supabase का client SDK स्वचालित रूप से पुनः कनेक्ट करने का प्रयास करेगा। लेकिन आपको हमेशा पुनः कनेक्शन पर वर्तमान लॉट state (current_bid, ends_at) को फिर से fetch करना चाहिए बजाय disconnection से पहले local state में जो कुछ था उस पर भरोसा करने के। अपने Client Component में एक online/offline event listener जोड़ें और जब कनेक्शन बहाल हो तो एक fresh server fetch को ट्रिगर करें।

क्या मैं एक API रूट की जगह Next.js Server Actions का उपयोग करके बिड प्लेस कर सकता हूं?

हाँ, और मैंने यह किया है। Next.js 14 में Server Actions सुविधाजनक हैं, वे एक समर्पित /api/bid रूट के बॉयलरप्लेट को हटाते हैं। ट्रेड-ऑफ़ यह है कि Server Actions को अलग से rate-limit करना थोड़ा कठिन है (आप rate limiting को middleware लेवल पर लागू करेंगे, प्रति-एक्शन के बजाय)। एक प्रोडक्शन नीलामी साइट के लिए, मैं middleware में Upstash Redis rate limiting जोड़ूँ, यह रोकने के लिए कि एक भी यूजर बिड रिक्वेस्ट को स्पैम न कर सके, भले ही आप Actions या API routes यूज़ करें।

मैं टाई को कैसे हैंडल करूँ, एक ही समय पर दो सामान्य बिड्स?

place_bid Postgres function में FOR UPDATE lock concurrent bids को serialize करता है, इसलिए technically database level पर ties नहीं हो सकते। एक सफल होगा, दूसरा "bid too low" response के साथ विफल होगा (चूंकि दोनों current_bid के बराबर हैं और check p_amount <= v_lot.current_bid है)। पहला आओ, पहले सेवा पाओ। यह standard auction practice है और अधिकांश bidders इसे समझते हैं।

---

बाथ की वह पुरावस्तु डीलर, जो कुछ भी हो, दो साल से ज़्यादा समय से अपनी मासिक नीलामियों को ऑनलाइन चला रहा है। एक शनिवार की शाम को समवर्ती बिडर्स का पीक 84 तक पहुँचा, उसका पूरा गाँव ज़ाहिरा तौर पर एक विवादास्पद जॉर्जियन सिल्वर लॉट को देखने के लिए ट्यून किया गया था जो अपने रिज़र्व से तीन गुना अधिक मूल्य में बिका। Supabase ने कोई हलचल नहीं की। Next.js ने कोई हलचल नहीं की। एकमात्र चीज़ जो टूटी वह उसका Wi-Fi था, क्योंकि वह इसे शॉप फ़्लोर से चला रहा था।

Real-time के बारे में सोचना मुश्किल है शुरुआत में, लेकिन एक बार स्कीमा ठोस है और atomic bid फंक्शन मौजूद है, बाकी ज़्यादातर पाइपिंग है। बुनियाद को सही रखें और आप अपना समय मज़ेदार चीज़ों पर लगाएँगे, काउंटडाउन एनिमेशन, "going once, going twice" यूएक्स, बजाय आधी रात को race conditions को डीबग करने के।

< BACK