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
- backend/routes/public-catalog.ts:L14-L35 —
catalogLimiter(60/min/IP) - backend/routes/public-catalog.ts:L38-L272 —
GET /:slugstorefront read - backend/routes/public-catalog.ts:L300-L718 — checkout + Razorpay create + inventory decrement
- backend/routes/public-catalog.ts:L724-L798 — Razorpay payment webhook
- backend/routes/catalog.ts:L68, L109 — catalog settings writes, fire
revalidateCatalog - backend/routes/store.ts:L127-L327 — store branding, checkout settings
- src/app/catalog/[slug]/page.tsx — storefront root, tagged fetch
- src/app/catalog/[slug]/p/[productSlug]/page.tsx — product detail
- src/app/catalog/[slug]/cart/page.tsx — cart
- src/app/catalog/[slug]/checkout/page.tsx + CheckoutPageClient.tsx
- src/contexts/CartContext.tsx — cart in
localStorage(keycatalog_cart)
7. Env vars & config
| Var | Required | Purpose |
|---|---|---|
NEXT_PUBLIC_API_URL | yes | FE → BE calls |
FRONTEND_URL | yes (prod) | BE → FE revalidation |
REVALIDATION_SECRET | yes (prod) | Signed invalidation |
RAZORPAY_KEY_ID / RAZORPAY_KEY_SECRET | yes | Checkout payments |
RAZORPAY_WEBHOOK_SECRET | yes | Payment verification |
8. Gotchas & troubleshooting
catalog_cartis NOT scoped by slug → CartContext.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_URLmismatch. Fire-and-forget swallows errors; check BE logs for[revalidateCatalog]warnings. See catalog-caching.md. - Catalog unpublishes on sub expiry →
jobs/update-expired-trials.tssilently flipspublished=falseif 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/checkoutPOST; 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.