All projects
livesince 2025

Discover Mumbai

An independent city guide to Mumbai, covering where to eat, what to see and how to get around, with the timings, prices and nearest stations that make a plan work.

  • React 18
  • TypeScript
  • Vite
  • Tailwind CSS
  • Supabase
  • Vercel
restaurants
152
landmarks
27
prerendered routes
217
event refreshes a week
2×

What it covers

  • 152 restaurants, scored on food, service, ambience and value, with hours, price range, dishes and FAQs
  • 27 landmarks and attractions, each with entry fees, time needed, nearest station and insider tips
  • Five itineraries (half-day to two-day) across 41 stops, each linking to its own guide
  • 11 neighbourhood pages, six “dining by occasion” pages, a blog, and full-text search across everything
  • A “What’s On” page listing upcoming events in the city

Engineering

Making a single-page app legible to search engines and AI assistants

The site is client-rendered, so crawlers that don’t execute JavaScript (GPTBot, PerplexityBot, ClaudeBot, Bing’s first pass) received an empty page.

I wrote a build step that renders all 217 routes to static HTML with per-page titles, canonical URLs, Open Graph tags and schema.org JSON-LD (Restaurant, TouristAttraction, TouristTrip, Event, FAQPage, BreadcrumbList), plus an llms.txt index. React hydrates over it, so visitors still get the interactive app. The build fails if any page comes out missing its bundle.

A scheduled agent for fresh content

Events are the one thing a static guide can’t keep current. A cloud agent runs twice a week, searches public listing sites, verifies each event against its source page, writes it up, upserts to Postgres and triggers a redeploy. It refuses to publish anything it couldn’t confirm on a real page.

Postgres generated tsvector columns with GIN indexes across four tables, exposed through a single SQL function, so new content is indexed on insert with no external service.

Performance for a mobile-first audience

Roughly 80% of visitors arrive on a phone, so images are served from Supabase Storage at three widths via srcset, heavy libraries are split into separate vendor chunks, and list components are memoised.

Stack

React 18, TypeScript, Vite, Tailwind CSS, React Router, Supabase (Postgres with row level security, Storage, Edge Functions), Vercel. 30 SQL migrations, ~12,500 lines across 99 source files.

What I learned

The hardest bugs were the invisible ones. The site was publishing a fabricated restaurant review in its structured data and rating counts of zero, both inherited from an earlier build, and neither was visible on the page. Google reported them before I did. A large part of the work was auditing what the site was quietly claiming about places it describes.

esc

Pages

HomeWorkAmazon picksAbout

Projects

Discover MumbailiveFlowscopeliveSubstack AgentbuildingNIFTY ATM Edgebuilding

Elsewhere

GitHubLinkedInSubstackX

Actions

Switch colour theme