Build a real-time auction platform with V0 using Next.js, Supabase Realtime, and Stripe. You'll get live bidding with instant updates, anti-sniping countdown extensions, secure payment processing for winners, and admin auction management — all in about 2-4 hours without local setup.
What you're building
Online auctions require real-time updates, race condition prevention, and reliable payment processing — three of the hardest problems in web development. Users need to see competing bids instantly, the system must prevent two people from bidding on stale prices, and winners need to pay seamlessly.
V0 handles the UI complexity by generating the auction listing grid, real-time bid feed, and checkout flow from prompts. Supabase via the Connect panel provides the database with Realtime channels for instant bid updates. Stripe via Vercel Marketplace handles winner payments.
The architecture uses Next.js Server Components for the auction listing and detail pages, a Client Component for the real-time bid feed subscribed to Supabase Realtime, an API route for bid validation with database-level transaction locking, a Stripe webhook handler for payment confirmation, and Server Actions for auction creation and management.
Final result
A fully functional auction platform with real-time bidding, anti-sniping protection, Stripe payment for winners, seller auction management, and a responsive auction gallery with countdown timers.
Tech stack
Prerequisites
- A V0 account (Premium plan recommended for complex multi-page builds)
- A Supabase project with Realtime enabled (free tier works — connect via V0's Connect panel)
- A Stripe account (test mode works for development)
- Understanding of auction mechanics (reserve prices, bid increments, time extensions)
Build steps
Set up the database schema with auction tables and bid constraints
Create a new V0 project, connect Supabase and Stripe via the Connect panel. Then prompt V0 to create the auction, bid, and payment tables with proper constraints. The schema includes a database trigger for anti-sniping that extends the auction end time when late bids arrive.
1// Paste this prompt into V0's AI chat:2// Build a real-time auction platform with Supabase. Create these tables:3// 1. auctions: id (uuid PK), seller_id (uuid FK to auth.users), title (text), description (text), images (text[]), starting_price (numeric), reserve_price (numeric), current_price (numeric), status (text CHECK in 'draft','active','ended','sold'), starts_at (timestamptz), ends_at (timestamptz), winner_id (uuid FK)4// 2. bids: id (uuid PK), auction_id (uuid FK), bidder_id (uuid FK to auth.users), amount (numeric), created_at (timestamptz), CONSTRAINT bid_positive CHECK (amount > 0)5// 3. payments: id (uuid PK), auction_id (uuid FK), buyer_id (uuid FK), stripe_payment_intent_id (text), amount (numeric), status (text), created_at (timestamptz)6// Add a database trigger: on bids INSERT, update auctions.current_price to the bid amount, and if the bid is within 5 minutes of ends_at, extend ends_at by 2 minutes (anti-sniping).7// Enable Realtime on the bids table.8// Add RLS policies.Pro tip: Enable Realtime on the bids table in the Supabase Dashboard under Database > Replication. This is required for the real-time bid feed to work on the auction detail page.
Expected result: Supabase is connected with tables created, the anti-sniping trigger is active, and Realtime is enabled on the bids table.
Build the bid submission API route with race condition prevention
Create the API route that validates and inserts bids. The key challenge is preventing two users from bidding on the same stale price simultaneously. Use a Supabase RPC function with SELECT FOR UPDATE to lock the auction row during bid validation.
1import { NextRequest, NextResponse } from 'next/server'2import { createClient } from '@supabase/supabase-js'34const supabase = createClient(5 process.env.SUPABASE_URL!,6 process.env.SUPABASE_SERVICE_ROLE_KEY!7)89export async function POST(req: NextRequest) {10 const { auctionId, bidderId, amount } = await req.json()1112 const { data, error } = await supabase.rpc('place_bid', {13 p_auction_id: auctionId,14 p_bidder_id: bidderId,15 p_amount: amount,16 p_min_increment: 1.0,17 })1819 if (error) {20 const message = error.message.includes('too low')21 ? 'Your bid is below the current price plus minimum increment'22 : error.message.includes('not active')23 ? 'This auction is not currently active'24 : error.message25 return NextResponse.json({ error: message }, { status: 400 })26 }2728 return NextResponse.json({ bid: data })29}3031// The RPC function in Supabase SQL editor:32// CREATE OR REPLACE FUNCTION place_bid(33// p_auction_id uuid, p_bidder_id uuid, p_amount numeric, p_min_increment numeric34// ) RETURNS uuid AS $$35// DECLARE36// v_auction record;37// v_bid_id uuid;38// BEGIN39// SELECT * INTO v_auction FROM auctions WHERE id = p_auction_id FOR UPDATE;40// IF v_auction.status != 'active' THEN RAISE EXCEPTION 'Auction not active';41// END IF;42// IF p_amount < v_auction.current_price + p_min_increment THEN43// RAISE EXCEPTION 'Bid too low';44// END IF;45// INSERT INTO bids (auction_id, bidder_id, amount)46// VALUES (p_auction_id, p_bidder_id, p_amount) RETURNING id INTO v_bid_id;47// UPDATE auctions SET current_price = p_amount WHERE id = p_auction_id;48// RETURN v_bid_id;49// END; $$ LANGUAGE plpgsql;Expected result: The bid API route calls a Supabase RPC function that locks the auction row, validates the bid amount is above the current price plus minimum increment, and inserts the bid atomically.
Build the real-time auction detail page with live bid feed
Create the auction detail page that shows the current price, countdown timer, bid history, and a bid input. Subscribe to Supabase Realtime on the bids table so all viewers see new bids within 200ms without polling.
1// Paste this prompt into V0's AI chat:2// Build a real-time auction detail page at app/auctions/[id]/page.tsx.3// Requirements:4// - Server Component fetches initial auction data and last 20 bids5// - Client Component wraps the bid feed with Supabase Realtime subscription6// - Subscribe to INSERT events on the bids table filtered by auction_id7// - Show current price in large font with animated update on new bids8// - Countdown timer that updates every second showing time remaining9// - Timer extends in real-time when anti-sniping trigger fires (subscribe to auction row changes)10// - Bid history in a ScrollArea showing bidder avatar, amount, and timestamp11// - Bid input with shadcn/ui Input for amount and Button to submit12// - AlertDialog for bid confirmation showing the amount before submitting13// - Skeleton loading states while initial data loads14// - Badge for auction status (active, ended, sold)15// - Image carousel for auction images using shadcn/ui CarouselPro tip: Subscribe to both the bids table (for new bid inserts) and the auctions table (for end time extensions from the anti-sniping trigger) on the same channel. This keeps the countdown timer and bid feed in sync.
Expected result: The auction page shows live bid updates, a countdown timer that extends on late bids, an image carousel, and a bid submission form with confirmation dialog.
Create the Stripe payment flow for auction winners
When an auction ends, the winner needs to pay. Build the payment flow using Stripe Checkout — create a session with the winning bid amount and handle the webhook to confirm payment and update the auction status to 'sold'.
1import { NextRequest, NextResponse } from 'next/server'2import Stripe from 'stripe'3import { createClient } from '@supabase/supabase-js'45const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)6const supabase = createClient(7 process.env.SUPABASE_URL!,8 process.env.SUPABASE_SERVICE_ROLE_KEY!9)1011export async function POST(req: NextRequest) {12 const body = await req.text()13 const sig = req.headers.get('stripe-signature')!1415 let event: Stripe.Event16 try {17 event = stripe.webhooks.constructEvent(18 body,19 sig,20 process.env.STRIPE_WEBHOOK_SECRET!21 )22 } catch {23 return NextResponse.json({ error: 'Invalid signature' }, { status: 400 })24 }2526 if (event.type === 'checkout.session.completed') {27 const session = event.data.object as Stripe.Checkout.Session28 const auctionId = session.metadata?.auction_id2930 if (auctionId) {31 await supabase.from('payments').insert({32 auction_id: auctionId,33 buyer_id: session.metadata?.buyer_id,34 stripe_payment_intent_id: session.payment_intent as string,35 amount: (session.amount_total ?? 0) / 100,36 status: 'paid',37 })3839 await supabase40 .from('auctions')41 .update({ status: 'sold' })42 .eq('id', auctionId)43 }44 }4546 return NextResponse.json({ received: true })47}Pro tip: Always use request.text() — not request.json() — for the Stripe webhook body. Stripe signature verification requires the raw body, and parsing it first corrupts the signature check.
Expected result: When an auction winner completes Stripe Checkout, the webhook creates a payment record and updates the auction status to sold.
Build the auction listing grid and seller creation form
Create the public auction listing page and the seller's auction creation form. The listing page shows active auctions in a responsive grid with countdown timers, current prices, and bid counts. Sellers can create new auctions with images, descriptions, reserve prices, and scheduling.
1// Paste this prompt into V0's AI chat:2// Build two pages:3// 1. Auction listing at app/auctions/page.tsx (Server Component):4// - Grid of auction Cards with image, title, current price, time remaining countdown, bid count5// - Badge for auction status (active, ending soon for < 1 hour)6// - Select for category filter and sort (ending soonest, highest price, newest)7// - Skeleton loading states8// - Link to detail page9// 2. Create auction at app/auctions/new/page.tsx ('use client'):10// - Form with Input for title, Textarea for description11// - Image upload (multiple) to Supabase Storage12// - Input for starting_price and reserve_price13// - DatePicker for starts_at and ends_at14// - Button to submit via Server Action15// - Form validation with zodExpected result: The listing page shows a responsive grid of active auctions with countdown timers. Sellers can create new auctions with images, pricing, and scheduled start/end times.
Complete code
1import { NextRequest, NextResponse } from 'next/server'2import { createClient } from '@supabase/supabase-js'34const supabase = createClient(5 process.env.SUPABASE_URL!,6 process.env.SUPABASE_SERVICE_ROLE_KEY!7)89export async function POST(req: NextRequest) {10 const { auctionId, bidderId, amount } = await req.json()1112 if (!auctionId || !bidderId || !amount) {13 return NextResponse.json(14 { error: 'Missing required fields' },15 { status: 400 }16 )17 }1819 const { data, error } = await supabase.rpc('place_bid', {20 p_auction_id: auctionId,21 p_bidder_id: bidderId,22 p_amount: amount,23 p_min_increment: 1.0,24 })2526 if (error) {27 const status = error.message.includes('not active') ? 409 : 40028 return NextResponse.json({ error: error.message }, { status })29 }3031 return NextResponse.json({ bid_id: data })32}3334export async function GET(req: NextRequest) {35 const { searchParams } = new URL(req.url)36 const auctionId = searchParams.get('auction_id')3738 if (!auctionId) {39 return NextResponse.json(40 { error: 'auction_id is required' },41 { status: 400 }42 )43 }4445 const { data, error } = await supabase46 .from('bids')47 .select('id, amount, created_at, bidder_id')48 .eq('auction_id', auctionId)49 .order('created_at', { ascending: false })50 .limit(50)5152 if (error) {53 return NextResponse.json({ error: error.message }, { status: 500 })54 }5556 return NextResponse.json({ bids: data })57}Customization ideas
Add proxy bidding
Implement automatic bidding where users set a maximum bid and the system automatically outbids competitors by the minimum increment up to their max, using a Supabase trigger on bid insert.
Add auction categories and search
Add a categories table and full-text search on auction titles and descriptions using Supabase's built-in tsvector and GIN indexes.
Add email notifications for outbid events
When a bidder is outbid, send them an email notification using Resend called from the bid trigger or an API route so they can re-bid before the auction ends.
Add a watch list
Let users save auctions to a watch list with a toggle button and show upcoming ending times for watched auctions on their dashboard.
Add seller ratings
After an auction is completed and paid, allow buyers to rate sellers with a 1-5 star rating and text review, displayed on the seller's profile and future auctions.
Common pitfalls
Pitfall: Validating bid amounts in JavaScript instead of the database
How to avoid: Use a Supabase RPC function with SELECT FOR UPDATE that locks the auction row, validates the bid is above current_price + min_increment, and inserts atomically — all within a single transaction.
Pitfall: Using request.json() for Stripe webhook body parsing
How to avoid: Always use request.text() to get the raw body, then pass it to stripe.webhooks.constructEvent() along with the stripe-signature header and your webhook secret.
Pitfall: Not enabling Supabase Realtime on the bids table
How to avoid: Go to Supabase Dashboard, navigate to Database, then Replication, and enable Realtime for the bids table. Also enable it for the auctions table to sync countdown timer extensions.
Pitfall: Not handling the anti-sniping timer extension on the frontend
How to avoid: Subscribe to changes on the auctions table via Supabase Realtime. When an UPDATE event arrives with a new ends_at value, update the countdown timer's target time.
Best practices
- Use Supabase RPC functions with SELECT FOR UPDATE for bid validation to prevent race conditions at the database level
- Enable Realtime on both the bids and auctions tables — bids for the live feed, auctions for anti-sniping timer extensions
- Always use request.text() for Stripe webhook body parsing to preserve the raw bytes for signature verification
- Store STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET in V0's Vars tab without NEXT_PUBLIC_ prefix
- Implement anti-sniping as a database trigger so it fires automatically regardless of how bids are inserted
- Use Server Components for the auction listing page and Client Components only for the real-time bid feed and countdown timer
- Use Design Mode (Option+D) to visually adjust auction card layouts, countdown timer styling, and bid feed density without spending credits
- Add Skeleton loading states to all auction pages since real-time data may take a moment to hydrate
AI prompts to try
Copy these prompts to build this project faster.
I'm building a real-time auction platform with Next.js App Router, Supabase, and Stripe. I need to handle bid race conditions, real-time bid updates, anti-sniping countdown extensions, and payment processing for winners. Help me design a Supabase RPC function that uses SELECT FOR UPDATE to atomically validate and insert bids.
Build a real-time bid feed component that subscribes to Supabase Realtime INSERT events on the bids table filtered by auction_id. The component should: display new bids instantly in a ScrollArea with auto-scroll to bottom, animate the current price update with a brief highlight, update the bid count, and merge incoming bids with the initial server-fetched list without duplicates by checking bid id.
Frequently asked questions
How does the anti-sniping protection work?
A database trigger fires on every bid insert. If the bid arrives within 5 minutes of the auction's end time, the trigger automatically extends ends_at by 2 minutes. This prevents last-second sniping and gives all bidders a fair chance to respond. The frontend detects the time extension via Supabase Realtime.
How do I prevent two people from placing the same bid simultaneously?
Use a Supabase RPC function with SELECT FOR UPDATE that locks the auction row within a transaction. It checks that the new bid exceeds current_price plus the minimum increment, inserts the bid, and updates current_price atomically. If two bids arrive simultaneously, one waits for the lock and then re-checks the price.
What V0 plan do I need for an auction platform?
V0 Premium is recommended because the auction platform requires multiple complex pages (listing, detail with real-time, creation form), API routes (bids, payments, webhooks), and Supabase Realtime integration. The free plan's credits will not be sufficient.
Can the free Supabase tier handle real-time auctions?
Yes for development and small-scale use. The free tier supports 200 concurrent Realtime connections, which is enough for testing and early launches. For production auctions with many simultaneous viewers, upgrade to Supabase Pro.
How do I deploy the auction platform?
Click Share then Publish to Production in V0 for instant Vercel deployment. After publishing, register the Stripe webhook URL at https://yourdomain.vercel.app/api/webhooks/stripe in the Stripe Dashboard. Select the checkout.session.completed event.
Can RapidDev help build a custom auction platform?
Yes. RapidDev has built 600+ apps including complex real-time platforms with bidding systems, escrow payments, and fraud detection. Book a free consultation to discuss your specific auction platform requirements.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation