Follow official docs only. Use the URLs, npm packages, and key locations in PLATFORM DETAILS. Do not invent SDK method names — look them up from those links.
Next.js + shadcn + Tailwind
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Next.js App Router: https://nextjs.org/docs/app
- Route Handlers: https://nextjs.org/docs/app/building-your-application/routing/route-handlers
- Environment variables: https://nextjs.org/docs/app/building-your-application/configuring/environment-variables
- shadcn install (Next.js): https://ui.shadcn.com/docs/installation/next
- shadcn components: https://ui.shadcn.com/docs/components
- Tailwind v3 + Next.js: https://v3.tailwindcss.com/docs/guides/nextjs
- Vercel deploy: https://vercel.com/docs/getting-started-with-vercel
- Vercel env vars: https://vercel.com/docs/projects/environment-variables
NPM PACKAGES
- next
- react
- tailwindcss
- class-variance-authority
- lucide-react
API KEYS — name and exactly where to create them
- NEXT_PUBLIC_SITE_URL: Your public origin, e.g. http://localhost:3000 or https://yourdomain.com
SETUP STEPS
npx create-next-app@latest . --ts --tailwind --app --src-dir npx shadcn@latest init npx shadcn@latest add button input card badge
Firebase / Firestore
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Firebase Console: https://console.firebase.google.com/
- Create a web app: https://firebase.google.com/docs/web/setup
- Firestore: https://firebase.google.com/docs/firestore
- Firestore security rules: https://firebase.google.com/docs/firestore/security/get-started
- Admin / service account: https://firebase.google.com/docs/admin/setup
- firebase npm: https://www.npmjs.com/package/firebase
- firebase-admin npm: https://www.npmjs.com/package/firebase-admin
NPM PACKAGES
- firebase
- firebase-admin
API KEYS — name and exactly where to create them
- NEXT_PUBLIC_FIREBASE_API_KEY: Firebase Console → Project settings → Your apps → Web app → apiKey
- NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: Same page → authDomain
- NEXT_PUBLIC_FIREBASE_PROJECT_ID: Same page → projectId
- NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: Same page → storageBucket
- NEXT_PUBLIC_FIREBASE_APP_ID: Same page → appId
- FIREBASE_SERVICE_ACCOUNT_JSON: Project settings → Service accounts → Generate new private key. Store the JSON as one env string (server only).
SETUP STEPS
- https://console.firebase.google.com/ → Create project
- Build → Firestore Database → Create (test mode only for local, then lock rules)
- Project settings (gear) → General → Your apps → Add web app → copy the firebaseConfig object
- Project settings → Service accounts → Generate new private key → FIREBASE_SERVICE_ACCOUNT_JSON
- Never put the service account in NEXT_PUBLIC_*
Stripe
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Stripe Dashboard: https://dashboard.stripe.com
- API keys: https://dashboard.stripe.com/apikeys
- Webhooks: https://dashboard.stripe.com/webhooks
- Checkout Sessions: https://docs.stripe.com/payments/checkout/how-checkout-works
- Create a Checkout Session API: https://docs.stripe.com/api/checkout/sessions/create
- stripe npm: https://www.npmjs.com/package/stripe
- Stripe CLI (local webhooks): https://docs.stripe.com/stripe-cli
- Test cards: https://docs.stripe.com/testing#cards
NPM PACKAGES
- stripe
API KEYS — name and exactly where to create them
- STRIPE_SECRET_KEY: https://dashboard.stripe.com/apikeys → Secret key (sk_test_… locally, sk_live_… in prod). Server only.
- NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: Same page → Publishable key (pk_test_…). Only if you use Stripe.js; Checkout redirect can skip it.
- STRIPE_WEBHOOK_SECRET: https://dashboard.stripe.com/webhooks → Add endpoint → Signing secret (whsec_…). Locally: stripe listen --forward-to localhost:3000/api/webhooks/stripe
SETUP STEPS
- https://dashboard.stripe.com → sign up, stay in Test mode
- Developers → API keys → STRIPE_SECRET_KEY
- Create Checkout Session: mode=payment, line_items price_data.unit_amount = bid cents, metadata.pending_id
- Success URL: {SITE}/success?checkout_id={pending_id}
- Webhook endpoint: {SITE}/api/webhooks/stripe event checkout.session.completed
- Local: stripe listen --forward-to localhost:3000/api/webhooks/stripe → STRIPE_WEBHOOK_SECRET
- Test card: 4242 4242 4242 4242
You are scaffolding a pay-to-rank bidding board (same product as ihatereading.lol).
PRODUCT
A public leaderboard. Anyone pastes a URL (or listing), types a bid of $1+, pays, and that listing rises by CUMULATIVE money. Highest total bid is #1. No user accounts. Anonymous is fine.
USER JOURNEY
- Land on / — hero + bid form + live board
- Paste URL → preview title/image/description (Open Graph)
- Set bid (number stepper, min $1 / 100 cents). Default the input to the current highest board bid.
- Pay → redirect to provider checkout
- Return /success?checkout_id=... → poll status until live → show rank + confetti
- Board shows rank, thumbnail, title, total $, bid count. Click a row → /listing/[id] to add another bid (stack).
DOMAIN (use these names, do not invent a different model)
- listings: id, url, type, title, description, thumbnail_url, favicon_url, author_name, submitted_by_name, total_bid_cents, bid_count, bid_cents_last_24h, status (pending|live|rejected), created_at, last_bid_at
- bids: id, listing_id, amount_cents, bidder_name, provider, checkout_id, payment_id, customer_email, created_at
- pending_checkouts: id, url, preview fields, bid_amount_cents, existing_listing_id (null = new URL), provider_session_id, created_at
- Optional: presence + stats counters for "N online" / views
RULES (must implement)
- Min bid $1 (400 if amount_cents < 100)
- Normalize URLs (https, strip tracking params). Same URL cannot create a second listing — return 409 with existing_listing_id; user must stack a bid instead
- Stacking: paying against existing_listing_id adds amount to total_bid_cents and bid_count
- Rank all-time: ORDER BY total_bid_cents DESC, last_bid_at DESC
- Rank trending: ORDER BY bid_cents_last_24h DESC
- Payments are idempotent: never insert the same payment_id twice
- Flow: create pending_checkout → create provider checkout with metadata { pending_id, bid_amount_cents } → webhook OR success-page poll fulfills → create/update listing + insert bid
- Server-only writes for money (never let the browser increment totals)
- No auth required
- Admin page optional: password gate to reject a listing
PAGES
- / landing (form + board)
- /listing/[id] detail + stack bid + bid history
- /success payment confirmation
- /stats simple counters
- shadcn/ui + Tailwind. Light theme. Mobile-first: stack board cards on small screens.
APIS
- POST /api/preview { url }
- POST /api/checkout { url, preview, bid_amount_cents, existing_listing_id? }
- GET /api/status?checkout_id=
- POST /api/webhooks/[provider]
- GET /api/board?sort=all_time|trending
SHIP
- .env.example with every key AND a comment above each key: what it is + the exact dashboard URL to create it
- README: numbered setup using the PLATFORM DETAILS links below
- Seed + clear scripts
- TypeScript strict. Do not add extra features (auth, AI ranking, comments).
STACK (do not substitute)
- Next.js 14+ App Router, TypeScript, Tailwind CSS, shadcn/ui, React Query or SWR
- Database: Firebase Firestore
- Payments: Stripe Checkout (Pay What You Want / adaptive pricing)
ADAPTER RULES
Firestore collections: listings, bids, pending_checkouts.
Use firebase-admin in API routes for all writes. Client may read listings for the live board.
Stripe: checkout.sessions.create with mode=payment, line_items price_data unit_amount = bid_amount_cents, metadata.pending_id.
Webhook: checkout.session.completed. Fulfill from session.metadata.pending_id.
Success URL: {SITE}/success?checkout_id={pending_id}
Idempotency key = Stripe session.id / payment_intent.
ENV KEYS for .env.example (comment each with the dashboard URL from PLATFORM DETAILS)
- NEXT_PUBLIC_SITE_URL
- NEXT_PUBLIC_FIREBASE_API_KEY
- NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN
- NEXT_PUBLIC_FIREBASE_PROJECT_ID
- NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET
- NEXT_PUBLIC_FIREBASE_APP_ID
- FIREBASE_SERVICE_ACCOUNT_JSON
- STRIPE_SECRET_KEY
- STRIPE_WEBHOOK_SECRET
Copy every official link from PLATFORM DETAILS into README.md under Setup.
Build the complete runnable app now. After files exist, print: install, env, migrate/seed, webhook listen, npm run dev.