Core
Catalog
Catalog Overview

Public Catalog — Overview

The customer-facing storefront: per-store slugged site where anonymous shoppers browse, add to cart, and check out. Served by Next.js RSC with a cached read path and a rate-limited write path for checkout.

1. Overview

Every OWNER has exactly one Store with a unique slug. The storefront renders at /catalog/[slug] and is the only route that accepts anonymous traffic. Products, catalog settings (theme, visibility), and store branding come from one cached backend read; cart state lives entirely in the browser (localStorage); checkout is a separate rate-limited write path that creates the Order and hands off to Razorpay.

2. Architecture

3. Data model

See schema.prisma: Store L75, CatalogSettings L111, Product L316, ProductVariant L359.

4. Key flows

4.1 Shopper browsing

4.2 Checkout

5. Lifecycle — catalog visibility

On subscription expiry, jobs/update-expired-trials.ts unpublishes the catalog if the plan included Shareable Catalog.

6. Key files

7. Env vars & config

VarRequiredPurpose
NEXT_PUBLIC_API_URLyesFE → BE calls
FRONTEND_URLyes (prod)BE → FE revalidation
REVALIDATION_SECRETyes (prod)Signed invalidation
RAZORPAY_KEY_ID / RAZORPAY_KEY_SECRETyesCheckout payments
RAZORPAY_WEBHOOK_SECRETyesPayment verification

8. Gotchas & troubleshooting

  • catalog_cart is NOT scoped by slugCartContext.tsx. Visiting store B after shopping in store A surfaces store A's cart. Real cross-store contamination bug.
  • Rate limit is per-IP, pre-cache → Only MISSes count. A cache HIT bypasses Express. Don't rely on 60/min/IP for DDoS.
  • Seller edits appear stale → Almost always REVALIDATION_SECRET / FRONTEND_URL mismatch. Fire-and-forget swallows errors; check BE logs for [revalidateCatalog] warnings. See catalog-caching.md.
  • Catalog unpublishes on sub expiryjobs/update-expired-trials.ts silently flips published=false if plan lost Shareable Catalog.
  • Checkout double-submit → Inventory + Order in one tx, but the client doesn't disable the button across the async Razorpay open. Idempotency comes from Razorpay's receipt, not our code.
  • Missing PDP slug returns styled 404 → Not a clear error; users may not realize the product was removed/renamed.

9. Extension points

  • Add a new storefront page → fetch with { next: { tags: [catalog-${slug}] } } to share the invalidation tag.
  • Add a checkout step → extend CheckoutPageClient + validate in /checkout POST; keep inventory + order create in one transaction.
  • Add a payment provider → new webhook endpoint alongside Razorpay at public-catalog.ts:L724; verify signature in middleware.

10. Related docs