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.
Search
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.