> Initializing Session...
Built in 10 Days • React 19 • TanStack Start

I built and shipped a complete e-commerce SaaS platform solo at vurlo.store. It integrates Firebase Auth, granular Firestore security rules, Razorpay checkout, and an interactive AI chatbot helper. Engineered for performance with lazy-loading, product quick-view modals, and server-side SEO & GEO optimization, all managed via a custom admin panel.
10 days
Founder & Full-Stack Engineer
Production
2026
Production SaaS
React 19 / Firebase
Understanding the core problems, proposed solutions, and realized product achievements.
Firestore has no "hold this unit while someone pays" primitive — two people can buy the last lamp at the same second if stock isn't locked at the right moment.
Customer A adds the last Saturn Lamp (stock: 1) to cart, hits checkout.
Customer B does the exact same thing, same product, same second.
Both clients read products/{id}.stock → both see 1. Both pass the "is there enough stock" check.
Both writes land. Stock goes 1 → 0 → then to -1 if nothing stops the second write.
Razorpay confirms both payments. Vurlo just sold a lamp it doesn't have.
use-cart.tsxplaceOrder() opens one runTransaction per cart line item and re-reads stock at commit time — not whatever the client fetched on page load.
use-cart.tsxIf stock < quantity inside the transaction body, it throws. Firestore retries the whole read/write pair on conflict — I'm not writing retry logic by hand.
use-cart.tsxOne writeBatch creates the order doc, clears the cart subcollection, and writes a notification — all committed together, so there's no half-created order.
checkout-component.tsxSignature mismatch, modal dismiss, SDK constructor throw, raw catch — all four delete the pending order and run a compensating runTransaction to add stock back.
Zero oversells since launch — COD and Razorpay both run through the same reserve → commit → verify pipeline.
Visual system flow diagrams representing data pipelines and structured checks.
automatic page routes matched directly to your project folders — uses @tanstack/router-plugin to watch the file tree at build-time and compile routeTree.gen.ts, removing any need to write manual routing configs.
keeps client cart state fully synced across open tabs and guest sessions — uses a live onSnapshot listener for accounts, a localStorage fallback for guests, and performanceMerge() to reconcile both when a user logs in.
manages shipping validations, real-time coupon calculations, and checkout branching — lazy-loaded to keep load times down, splitting execution flows cleanly between Cash-on-Delivery and Razorpay online payments.
reserves stock atomically inside a database transaction so two people never buy the same last item — reads current stock, updates it, and commits checkout details alongside cart clearing in a single atomic writeBatch.
a secure backend helper to initiate online credit/debit card orders — instantiates the Razorpay SDK server-side to generate order tokens securely without ever exposing keys to the client web browser.
the check that stops someone faking a 'payment successful' callback and getting a free order — recomputes crypto.createHmac('sha256', RAZORPAY_KEY_SECRET) over `${order_id}|${payment_id}` and compares it against razorpay_signature before firebase-admin marks the order paid. no timing-safe compare, works fine for now but I'd swap in crypto.timingSafeEqual if I revisit this
in plain terms: a customer can cancel their own pending order and change nothing else about it. Enforced by `diff(resource.data).affectedKeys().hasOnly(['status'])` on the top-level orders/{orderId} rule — users/{uid}/orders/{orderId} itself is hard-locked to `allow read, write: if false` so there's no back door in
sends confirmation emails asynchronously without slowing down checkout — fires a background notification request to Resend after checkout commits, ensuring slow mail servers never hold up the customer's success screen.
uses AI to suggest complementary products directly on the cart page — sends current cart catalog items to Gemini 2.0 Flash and returns matching product IDs, backed by an in-memory cache to save on API costs.
Key features and built-in components implemented inside the codebase.
A deep dive into security rule architectures, race conditions, and verification solutions.
Between 'add to cart' and Razorpay's async confirmation, nothing stopped the same lamp being bought twice.
Two customers could both end up thinking they'd bought the last lamp. Firestore has no built-in way to say 'only one request touches this stock count at a time' (what databases call row-locking) — two placeOrder() calls reading the same field at the same instant can both pass the check and both decrement. That's overselling by construction, not a bug.
use-cart.tsx"Firestore transactions are scoped per-document, not per-request. Protecting a multi-item order means N separate transactions, not one transaction wrapped around the whole cart — I got that wrong on the first pass."
A chronological day-by-day log detailing core milestones and features added.
TanStack Start scaffold, Tailwind/shadcn config, src/routes/ tree established.
firebase.json set up; Auth + Firestore SDK wired into use-auth.tsx and lib/firebase.ts.
use-cart.tsx built out — onSnapshot cart, localStorage guest fallback, merge-on-login batch.
firestore.rules written — admin role gating, field-diff protections, nested orders lockdown.
checkout-component.tsx multi-step flow shipped, coupon validation against the coupons collection.
create-razorpay-order.ts and verify-razorpay-payment.ts built, HMAC verification wired into every exit path.
admin.tsx role-gated layout shipped with products, orders, coupons, stock-request panels.
Gemini recommendations, Resend emails, image pipeline, manualChunks split, sitemap gen before deploy.
Audited system lighthouse stats for performance, accessibility, best practices, and SEO.
Concise rationales for technology selections and future roadmaps.
Routes are plain files under src/routes/; @tanstack/router-plugin compiles them into routeTree.gen.ts so I'm not hand-maintaining a route config object anywhere.
checkout-component.tsx (906 lines) and the admin dashboard are both React.lazy imports behind Suspense fallbacks — keeps checkout/admin code out of the anonymous storefront bundle entirely.
runTransaction runs once per product doc in both placeOrder() and reserveStockAndGetTotal() (use-cart.tsx), so two simultaneous checkouts on the same item can't both win the stock check.
rollupOptions.output.manualChunks explicitly separates vendor, firebase, router, query, and ui — a Firestore SDK bump doesn't blow away the React vendor chunk's cache.
verify-razorpay-payment.ts is the only place RAZORPAY_KEY_SECRET recomputes the HMAC signature, and the only code holding firebase-admin credentials to flip an order to paid. The browser never sees either.
"Evaluating scaling adjustments and cache controls shows true engineering maturity."
Scrollable preview tracks showcasing panel responsive structures.
End the workspace session to release resources and return back to the primary portfolio index.