CinematicTale (cinematictale.com) turns a one-sentence prompt into a fully illustrated storybook, comic, or short AI video — powered by Google Gemini for text, Imagen 4 for illustrations, and Veo 3 for video. I built and operate the whole stack: a Next.js 16 App Router frontend, a Firebase backend, and a Razorpay-based subscription economy, run across fully isolated UAT and production environments.
The interesting part of building a product like this isn't the demo — it's everything that breaks once real users, real money, and real infrastructure enter the picture. A few of those moments taught me more than the initial build did.
The bug that returned an empty page with no error
Weeks after launch, the Explore gallery and individual story pages started silently returning empty — no error, no stack trace, just nothing. The obvious suspects (Firestore security rules, environment variables, a bad deploy) all checked out clean. The actual cause turned out to be a single missing argument: `getFirestore(app)` needs an explicit `'default'` database ID on some project/region configurations, or it fails with a bare, unhelpful `5 NOT_FOUND` error that gets swallowed by an outer try/catch. The fix was one line — `getFirestore(app, 'default')` — but finding it meant reproducing production locally with real credentials and reading raw server logs, because every layer above that point was designed to fail gracefully and hide exactly the information I needed.
The lesson that stuck: a `.catch(() => [])` that keeps your UI from crashing is also a `.catch()` that can hide a real infrastructure bug behind an innocent-looking empty state. Fail loud in development, fail quiet in production — but log the loud version somewhere you'll actually see it.
Pricing a product across two currencies and two economies
Adding INR pricing alongside USD wasn't a matter of multiplying by an exchange rate. The AI generation costs (Gemini, Imagen, Veo) are billed in USD regardless of what currency the customer pays in, so every regional price has to be checked against real unit economics, not just converted. Working through the actual numbers — cost per credit, margin per plan, and specifically the video generation tier, where a naive INR price would have made every video clip a guaranteed loss — turned pricing from a marketing decision into an engineering constraint.
The pattern that emerged: currency is a checkout-time concern (Razorpay Plans are immutable, currency-scoped objects with no shared pricing across them), but margin is a product-design concern that has to be solved before a single price is typed into a dashboard.
The three-domain bug hiding behind one console error
Firebase Analytics reported nothing in Realtime, despite the SDK being correctly wired and deployed. The browser console eventually surfaced the real cause: a Content-Security-Policy header — added months earlier for legitimate XSS protection — didn't whitelist Google's analytics collection domains, so every event was silently blocked as a policy violation. No error reached the application code; the browser just declined to make the request. Underneath that was a second, independent bug: the Firebase project's actual GA4 measurement ID no longer matched what was hardcoded in the environment config, a drift that had happened invisibly at some point after initial setup.
Two unrelated bugs, both invisible unless you specifically go looking in the browser console rather than the application logs — a good reminder that "the code is correct" and "the browser will actually run it" are separate claims, especially once security headers, ad blockers, and third-party script loaders enter the picture.
Shipping isn't done until the deploy is confirmed
More than once, a fix that was correct, tested, committed, and pushed still wasn't live — because "pushed to a branch" and "deployed to production" turned out to be two different facts that needed independently verifying. The habit that saved the most time: after any fix, curl the actual production response — the real HTTP headers, the real HTML, the real JS bundle — rather than trusting that a green commit means a resolved issue. Git tells you what you intended to ship. Only the live server tells you what's actually running.
What this project is, mechanically
Frontend: Next.js 16 App Router, React 19, TypeScript, Tailwind CSS v4. AI: Google Gemini for text, Imagen 4 for images, and Veo 3 for video, behind a single-provider adapter layer with circuit breakers and rate limiting. Backend: Firebase (Auth, Firestore, Storage) across fully isolated UAT and production projects. Payments: Razorpay subscriptions plus one-time credit top-ups, dual currency (USD/INR), a webhook-driven credit ledger built on atomic Firestore transactions. Ops: Vercel hosting, Vercel Analytics and Firebase Analytics (GA4), Firestore composite indexes managed through the Firebase CLI.
The product itself is the visible part. The real engineering was in the parts nobody sees until they break: the database call that needs one extra argument, the pricing model that has to survive contact with real API bills, the security header that quietly disables the feature it wasn't trying to touch, and the deploy pipeline that has to be checked, not assumed.
— Saurabh