Appearance
QR Setu Tech Stack
The canonical record of what QRSETU runs on: the current stack, the target stack it is moving to, why, and what each costs. Kept in sync with the architecture ADRs (esp. ADR-0011).
Guiding principle — zero-burn, free-tier-first
Early-stage budget discipline: the only paid service today is Supabase Pro. Every tech choice below is measured against that — no new paid vendors, everything on open-source frameworks and free tiers, with costs appearing only usage-driven as we scale (never upfront). Framework choices are cost-neutral (all open-source, MIT).
Why we're changing the frontend — the SSR/SEO win
This is the decision that motivates the migration, so it's stated plainly. The current frontend is a client-rendered Vite + React SPA: the browser downloads an HTML shell + a JS bundle, then renders the content after JS executes. That is actively bad for QRSETU's growth engine — the public service cards — in two concrete ways:
- Broken social/link previews. Social scrapers (WhatsApp, Facebook, X, Slack) do not execute JavaScript. They read the HTML they receive — which, for a client-rendered SPA, is an empty shell with generic meta tags. So a merchant sharing their QRSETU card on WhatsApp (the primary viral loop in India) gets a blank/generic preview instead of their business name, logo, and description. This is a growth bug hiding in the current stack.
- Weaker search indexing + slower first paint. Crawlers can render JS but do it slower and less reliably; the SPA's content isn't in the initial HTML, per-slug meta isn't set at HTTP-response time, and LCP is delayed by the JS round-trip.
The target frontend renders the public cards server-side (SSR/SSG): full HTML + correct per-slug OG/meta + JSON-LD structured data (LocalBusiness/Service) arrive in the HTTP response. Result: rich WhatsApp/social previews, reliable Google indexing (incl. rich results), and fast LCP — behind Cloudflare's cache (>95% hit target). For a product whose growth is shareable, discoverable business cards, this is a step-change, not a nice-to-have.
See ADR-0011 for the full frontend decision (surface-matched: web-DOM for public+admin, universal Expo/RN for the merchant app).
Current → Target mapping
| Layer | Current | Target | Why | New cost |
|---|---|---|---|---|
Landing / marketing (/, features, pricing, per-domain SEO pages) | Client-rendered Vite/React SPA | React Router v8 (SSR) on Cloudflare | SEO — the primary traffic driver (organic discovery → signups) | $0 (open-source; Cloudflare free tier) |
Public service cards (/b/:slug, /s/:slug, /w/:slug) | Client-rendered Vite/React SPA | React Router v8 (SSR) on Cloudflare — same React stack | SEO + WhatsApp previews + fast LCP (see above) | $0 |
| Merchant app (dashboard, editing, analytics) | Same web SPA | Expo / React Native — Android native (R1), iOS native (R1), responsive web via RNW | Mobile-first product; true native + structural parity | $0 frameworks; builds free (below) |
| Admin (command center + ops) | Web SPA (admin tier) | React Router v8 (React + shadcn, desktop-first) + installable PWA for mobile ops | Dense data UI stays best-in-class; mobile field-demo via PWA | $0 |
| UI components | shadcn/ui (Radix + Tailwind, DOM) | shadcn/ui KEPT for public + admin; RN primitives + NativeWind for the merchant app | Keep density/a11y where DOM is best; native where the product lives | $0 |
| Design tokens | Tailwind theme / CSS vars | One shared design-token package → compiled to a Tailwind config (DOM) + a NativeWind theme (RN) | Single source of truth; zero cross-stack drift | $0 |
| Styling | Tailwind CSS | Tailwind (DOM) + NativeWind (RN) | Same utility/token model across idioms | $0 |
| Routing | react-router (SPA) | Astro routing + React Router v8 (web) · Expo Router (mobile) | File-based, SSR-capable, one tree per stack | $0 |
| Language | JS/JSX + partial TS | TypeScript strict (all new code) + JS→TS migration roadmap | Type safety, shared portable core | $0 |
| Data access (client) | Mixed supabase.from() + React Query + cacheUtils.js | TanStack Query + RPC/Edge-Function services (retire from() + cacheUtils) | The enforced data-access rule; runs identically in DOM + RN | $0 |
| Backend / DB / Auth / Storage | Supabase (Postgres + RLS + Auth + EF + Storage) | Same Supabase + archetype platform (ADR-0009), entitlements (ADR-0007), analytics read model (ADR-0010) | No backend re-platforming — it's all UI-agnostic | $0 new (existing Supabase Pro) |
| Hosting | Cloudflare Pages (static) | Cloudflare Pages + Workers/Functions (SSR) + Expo web export | Same vendor; adds edge SSR | $0 free tier → $5/mo at scale |
| Mobile builds | — (none) | EAS Build (free tier) or local / GitHub Actions | Cloud or local RN app builds | $0 (free tier / local) |
| Payments | — (none) | Razorpay (web-first, companion model — ADR-0002) | India-first PSP; 0% store commission | $0 fixed (~2%/txn) |
| Supabase Auth built-in | ZeptoMail (transactional/OTP) + Zoho Campaigns (promotional, later — ADR-0008) | Deliverability, branded OTP, India DC | $0 to start (free tier → credits) | |
| CI/CD | Husky pre-commit; GitHub | GitHub Actions pipelines (frontend + backend) | Free CI minutes cover early scale | $0 (free tier) |
| Repo structure | Single repo / single app | Monorepo packages/{core,tokens} + apps/{web,mobile} (timing TBD) | "Extraction is a move, not a rewrite" | $0 |
Cost model (verified 2026-07-20)
| Service | Free allowance | When you'd pay | Paid tier |
|---|---|---|---|
| Frameworks (Astro / React Router / Expo / Next) | Unlimited — open-source | Never | — |
| Cloudflare Pages | Unlimited static requests/bandwidth; 500 builds/mo | — | — |
| Cloudflare Workers/Functions (SSR) | 100k requests/day + 10ms CPU/req | Sustained SSR beyond free (mitigated by edge caching + Astro static-first) | $5/mo (Workers Paid; no daily cap, more CPU) |
| EAS Build (RN app builds) | 15 Android + 15 iOS builds/mo (or build locally / GitHub Actions, free) | Frequent priority cloud builds | $19/mo (Starter) |
| Google Play | — | To publish Android | one-time $25 |
| Apple Developer | — | R2 (iOS native) — $0 in R1 | $99/yr |
| Razorpay | No fixed fee | Per transaction | ~2%/txn |
| ZeptoMail | 10k emails on signup | Higher send volume | credit-based (cheap) |
| Supabase | (existing — Pro, ~$25/mo) | Analytics/storage growth | usage add-ons |
Net new recurring cost for R1 = $0. The only guaranteed spend is a one-time $25 Google Play registration at publish. No new paid vendor is introduced — Cloudflare is already the host, and every framework is free.
First places cost will ever creep (so there are no surprises)
- Cloudflare Workers Paid ($5/mo) — if public SSR volume outgrows 100k req/day. Deferred by the >95% cache-hit design (a cache hit is served from Cloudflare's edge cache and doesn't re-invoke the Worker), so only cache misses run SSR.
- Supabase compute/storage add-ons — if analytics rollups/storage get heavy. Deferred by ADR-0010's pre-aggregated rollup design (dashboards read cheap, not raw tables).
Both are usage-driven, small, and arrive only at real scale — consistent with the zero-burn principle.
Frontend architecture at a glance (two stacks, one core)
The 4 tiers map to 2 stacks:
| Tier | Auth? | SEO? | → Stack |
|---|---|---|---|
landing (/, features, pricing, per-domain pages) | No | Yes — primary traffic driver | Stack 1 (RR8 SSR, indexed) |
public (service cards /b/:slug …) | No | Yes — viral/share engine | Stack 1 (RR8 SSR, indexed) |
user / merchant (/dashboard/*) | Yes | No | Stack 2 (Expo/RN app) |
admin (/admin/*) | Yes | No | Stack 1 (RR8 web + PWA) |
- Stack 1 — React web (React Router v8, SSR, DOM, shadcn): serves three tiers — landing + public (both indexed, SEO-critical, cached) and admin (behind login, + PWA). Auth pages (
/login,/signup) live here too butnoindex. - Stack 2 — Universal Expo/React Native: the merchant product app only (Android native / iOS native / responsive web via RNW).
- Shared across both: the platform-agnostic TS core (TanStack Query, Supabase, RPC/EF services, Zod, entitlement/vertical resolvers) and the design-token package (single source of truth for the look).
Programmatic-SEO growth lever: the landing tier can generate per-domain × use-case × city pages (/for/real-estate, /qr-menu-for-cafes, …) from the domain registry (ADR-0009), each SSR'd for high-intent local search — a major organic-growth play, cheap to build on RR8 SSR + the registry. Planned as a post-R1 growth workstream.
Frontend framework — decided (2026-07-20)
React Router v8 (formerly Remix) for both public + admin — Stack 1 is one React framework (ADR-0011 open-Q1, resolved). Chosen for solo-developer maintainability without compromising SEO: RR8 SSR delivers all the SEO fundamentals that drive growth (server HTML, per-slug OG for WhatsApp, JSON-LD, indexing, fast LCP) while keeping Stack 1 to a single React framework (one build, full shadcn reuse). Astro was the max-CWV alternative but would force two web frameworks (Astro can't do the dense admin) — not worth it for one person; its only edge is marginal Core Web Vitals. Escape hatch: the public pages alone can later migrate to Astro if measured CWV becomes a competitive bottleneck — surgical, reversible. Next.js ruled out (OpenNext adapter tax on Cloudflare, no upside here). Cost-neutral.
Related
- ADR-0011 · Frontend platform — the surface-matched decision this stack implements.
- ADR-0002 · Billing · ADR-0008 · Email — Razorpay / ZeptoMail choices + costs.
- ADR-0009 · Archetype platform · ADR-0010 · Analytics — the backend that stays on Supabase.
- Product Vision · Platform Overview.