How A Simple Assignment Became A Full-Stack Automated System (And Why Vercel's Timeout Message Haunts Me)
TL;DR: Built a MGNREGA dashboard for one state. Turned into automated system for all of India. Fought timeouts, laggy maps, and CSV hell. Won using GitHub Actions, CSS Grid, and zero paid features.
Day 0: The Assignment (Not the official Days, I didn't count them then)
Bharat Digital Fellowship 2026 Assignment:
- Choose a state (Maharashtra, Bihar, etc.)
- Build a dashboard with MGNREGA data
- Add district comparison tool
- Deadline: November 1st, 2025
My reaction: "Easy. I'll pick Maharashtra, throw up a React dashboard, add some charts. Done in 1 week."
Narrator: It was not done in around 5 days.
Day 1: The Data Nightmare
I logged into data.gov.in to fetch MGNREGA data.
My plan:
- Call API
- Get all data
- Display in dashboard
- Submit assignment
Reality:
fetch('https://api.data.gov.in/resource/...')
.then(res => res.json())
.then(data => {
// Still fetching...
// Still fetching...
// Still fetching...
// *30 minutes later*
// Still fetching...
});
The API wasn't slow. The data was massive.
The Numbers That Broke My Fetch Call
- 28,988 total metrics across all states
- 14,028 unique metrics after deduplication
- 740 districts across 36 states/UTs
- 35+ data points per district per month
- Updates: Daily
Problem: Fetching all this via API = infinite loop of doom.
Day 1.5: The CSV Pivot
New plan:
- Screw the API (for now)
- Download CSV files manually from data.gov.in
- Import to database
- Build dashboard from local data
Downloaded:
MAHARASHTRA_2024-2025.csv(19 months of data)MAHARASHTRA_2025-2026.csv(latest October data)
Result:
- 646 monthly records
- 34 districts
- Database imported successfully ✅
- Dashboard built ✅
- Submission on Nov 1st ✅
Assignment complete. Time to relax, right?
Me: "...but what if I did this for ALL of India?"
Day 2: The "Just For Fun" Spiral
Date: November 2nd, 2025
Time: 3 AM (all bad decisions happen at 3 AM)
Thought: "I can add filters to the API for all states and inject that data."
The All-India Challenge
Instead of just Maharashtra, let's get:
- All 36 states and UTs
- Both 2024-25 and 2025-26 financial years
- All 740 districts
- All 14,028 unique metrics
Added API filters:
for (const state of states) {
for (const year of financialYears) {
await fetchAndInjectData(state, year);
}
}
Result: Data injected successfully into Supabase! 🎉
Then reality hit: This data updates daily.
Manual updates? Hell no.
Day 2.5: The Cron Job That Never Was
The Plan: Add a cron job to sync data daily at 1 AM IST.
The Problem: Deployed on Vercel. Cron jobs = Paid feature. I'm broke.
New Plan: Use Vercel's Edge Functions with external cron service (cron-job.org) → Free!
// /api/cron/daily-sync
export async function GET(request: Request) {
// Verify secret
// Fetch latest data
// Update database
return Response.json({ success: true });
}
GitHub Actions workflow:
schedule:
- cron: '30 19 * * *' # 1:00 AM IST
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Call sync endpoint
run: |
curl -X GET "https://your-app.vercel.app/api/cron/daily-sync?secret=${{ secrets.CRON_SECRET }}"
Deployed. Triggered. Watched logs.
✅ GitHub Action started
✅ API endpoint called
⏰ Syncing data...
⏰ Still syncing...
⏰ Still syncing...
❌ Error: Function execution timeout (300 seconds)
Me: "WHAT."
Day 3: The Vercel Wall
The Problem:
- Vercel's max function timeout: 300 seconds (5 minutes)
- My data sync: 15-60 minutes depending on API rate limits
- Math: 15 minutes > 5 minutes → ❌ Timeout
Options:
- Pay for Vercel Pro (timeout up to 900s / 15 min) — $$$
- Split sync into smaller chunks — Complex, fragile
- Run sync directly on GitHub Actions — Free, reliable, no timeout
Guess which one I picked.
The Real Solution: Direct Database Sync
New architecture:
GitHub Actions Runner
↓
Install Node.js, pnpm
↓
pnpm install (frozen lockfile)
↓
Generate Prisma Client
↓
Run scripts/daily-sync.ts
↓
Connect directly to Supabase PostgreSQL
↓
Sync data (15-60 min, no timeout)
↓
Upload logs as artifacts
Key changes:
# .github/workflows/daily-sync.yml
jobs:
sync-data:
runs-on: ubuntu-latest
timeout-minutes: 180 # 3 hours (GitHub's max)
steps:
- name: Enable pnpm
run: corepack enable && corepack prepare pnpm@latest --activate
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Generate Prisma Client
run: pnpm prisma generate
- name: Run sync
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
DATA_GOV_API_KEY: ${{ secrets.DATA_GOV_API_KEY }}
run: pnpm run sync:cli
Result:
- ✅ Runs daily at 1:00 AM IST
- ✅ No timeouts (3-hour limit)
- ✅ Full logs uploaded as artifacts
- ✅ Resume support (if sync fails midway)
- ✅ Completely free (GitHub Free Tier)
Lesson learned: Sometimes the "hacky" solution is the right solution.
Day 3.5: The Map Saga (Or: When Simple Beats Complex)
Goal: Interactive India map where users can click states and navigate to state pages.
Attempt 1: The SVG Dream
Found: India GeoJSON file with precise state boundaries
Size: 210KB
Coordinates: Thousands of polygon points
Used: react-simple-maps library
<ComposableMap>
<Geographies geography={indiaGeoJSON}>
{({ geographies }) =>
geographies.map((geo) => (
<Geography
key={geo.rsmKey}
geography={geo}
onClick={() => navigate(`/state/${geo.id}`)}
/>
))
}
</Geographies>
</ComposableMap>
Result:
- ✅ Looks beautiful
- ✅ Precise boundaries
- ❌ Laggy as hell on mobile
- ❌ 19MB file size
- ❌ Re-renders on every hover
- ❌ Unusable on ₹3,000 phones
Me: "There has to be a better way."
Attempt 2: CSS Grid Simplicity
Idea: What if... I just use CSS Grid?
Implementation:
- 27 rows × 32 columns grid
- Each state = series of grid cells
- Cells merge using
grid-columnandgrid-row - Click handlers on each state div
- Tooltip on hover
// Simplified example
<div className="grid grid-cols-32 grid-rows-27">
{/* Kashmir */}
<div
className="col-start-13 col-span-7 row-start-1 row-span-5"
onClick={() => navigate('/state/jammu-kashmir')}
>
Jammu & Kashmir
</div>
{/* Repeat for all 36 states */}
</div>
Result:
- ✅ Instant load
- ✅ Zero lag on mobile
- ✅ 5KB CSS (vs 210KB GeoJSON)
- ✅ Works on ancient phones
- ✅ Easy to maintain
Trade-off: Not geographically precise, but functional and fast.
Lesson: Fancy isn't always better. Sometimes CSS Grid > Complex SVG.
Day 4: Search Bar With Voice (Because Why Not)
Goal: Search districts by name with voice input.
Why voice? Not everyone types well. Especially older users or regional language speakers.
Tech Stack:
- Fuse.js for fuzzy search (handles typos)
- Web Speech API (browser-native, zero dependencies)
- Keyboard navigation (↑↓ arrows, Enter, Escape)
Implementation:
// Voice input
const recognition = new webkitSpeechRecognition();
recognition.lang = 'en-IN'; // or hi-IN, mr-IN, etc.
recognition.onresult = (event) => {
const transcript = event.results[0][0].transcript;
setSearchQuery(transcript); // Auto-searches
};
// Fuzzy search
const fuse = new Fuse(districts, {
keys: ['name', 'stateName'],
threshold: 0.3, // 30% tolerance for typos
});
const results = fuse.search(searchQuery);
Features added:
- 🎤 Voice input button (click to speak)
- ⌨️ Keyboard navigation (arrows to move, Enter to select)
- 🔍 Fuzzy matching (handles typos like "Mumbia" → "Mumbai")
- 📱 Mobile-optimized (large touch targets)
Result: Search works in 9 languages with voice support. Zero external paid APIs.
Day 4.5: The i18n Rabbit Hole
Realization: 1.4 billion Indians don't all speak English.
Solution: Multi-language support.
Languages Added:
- English (en)
- Hindi (hi)
- Marathi (mr)
- Tamil (ta)
- Telugu (te)
- Malayalam (ml)
- Kannada (kn)
- Bengali (bn)
- Gujarati (gu)
Tool: next-intl for routing + i18n
Translation process:
- Extract all UI strings (25+ keys)
- Use AI (ChatGPT/Claude) for initial translations
- Manual review by native speakers (where possible)
- Store in JSON files (
messages/en.json,messages/hi.json, etc.)
Did I use AI? Yes. Obviously.
Why? Because manually translating 25 keys × 9 languages = 225 strings. Ain't nobody got time for that.
But: AI translations need review. Some phrases don't translate well. Cultural context matters.
Example:
- English: "Explore Districts"
- Hindi (AI): "जिलों का अन्वेषण करें" (technically correct)
- Hindi (Better): "जिले देखें" (more natural)
Lesson: Use AI as a starting point, not the finish line.
Next Chapter: The Next Evolution - Golang Backend (The 10x Future)
Current status: Working system. Data syncs daily. APIs respond fast. Users happy.
But here's the thing: I'm already hitting limits.
The Current Bottlenecks
1. Sync Time
- Current: 15-60 minutes for 740 districts
- GitHub Actions timeout: 180 minutes max
- Buffer: Only 2-3x (uncomfortable)
2. Memory Usage
- Current: 500MB during sync
- GitHub runner limit: 7GB (but shared)
- Vercel function limit: 1GB (serverless constraint)
3. Concurrency
- TypeScript Promise.all: 3-5 concurrent operations max
- Reason: Event loop saturation, memory pressure
- Result: Slow throughput
4. Cost
- Vercel Pro: $20/month (for cron jobs)
- Supabase Pro: $25/month (approaching limits)
- Total: $540/year (for a side project!)
The Golang Solution (The 10x Rewrite)
I'm planning a backend migration to Golang while keeping the TypeScript frontend. Here's why this isn't just "rewrite for fun":
1. Concurrency Model (The Game Changer)
Current TypeScript limitation:
// Max 3-5 concurrent operations before event loop saturates
await Promise.all(states.slice(0, 3).map(s => syncState(s)));
Golang goroutines:
// 50-100 concurrent operations easily
var wg sync.WaitGroup
for _, state := range states {
wg.Add(1)
go func(s State) {
defer wg.Done()
syncState(s) // Each runs in parallel
}(state)
}
wg.Wait()
Real impact:
- Sync time: 15-60 min → 5-15 min (3-4x faster)
- Throughput: 3 req/s → 50 req/s (16x)
- GitHub Actions: More buffer for future data growth
2. Memory Efficiency (Critical at Scale)
Current issue:
- Loading 28,988 metrics → 500MB RAM
- Node.js garbage collection pauses
- Memory spikes during sync
Golang stream processing:
rows, _ := db.Query("SELECT * FROM metrics WHERE finYear = ?", year)
defer rows.Close()
for rows.Next() { // One row at a time
var metric Metric
rows.Scan(&metric.ID, &metric.Value, ...)
processAndInsert(metric) // Immediate processing
// Memory freed after each iteration
}
Result:
- Memory usage: Constant 100-150MB (3-4x less)
- No GC pauses: Golang GC is microsecond-level
- Predictable performance
3. Deployment Simplicity
Current (Next.js + TypeScript):
FROM node:20-alpine # 150MB
COPY package.json .
RUN npm install # +200MB node_modulesCOPY . .
RUN npm run build # +50MB build artifacts# Final image: 400MB+
Golang (Single Binary):
FROM scratch # 0MB base
COPY main /main # 15MB binary (all dependencies included)CMD ["/main"]
# Final image: 15MB
Benefits:
- Deploy: Just copy one file to server
- Cold start: < 100ms (vs 2-3s for Node.js)
- No dependency hell: Everything compiled in
4. Cost Optimization
Current annual costs:
| Service | Current | With Golang | Savings |
|---|---|---|---|
| Compute | Vercel Pro $240 | VPS $60 | $180 |
| Database | Supabase $300 | Supabase Free $0 | $300 |
| GitHub Actions | $0 (barely) | $0 (comfortable) | Buffer |
| Total | $540/year | $60/year | $480 |
Why savings?
- Golang uses 5x less resources = Supabase stays in free tier
- Self-host on $5/month VPS instead of Vercel Pro
- Faster sync = more GitHub Actions free tier headroom
5. Real-World Validation
Companies that migrated Node.js → Golang:
PayPal:
- 35% faster response time
- 50% fewer servers needed
- Handled 10x more requests
Uber:
- Geofence service: 10x concurrency
- 80% memory reduction
- No more OOM crashes
Medium:
- API response: 500ms → 50ms
- User-facing pages 10x faster
- Happier users
The pattern: Heavy data systems benefit massively from Golang.
6. The Proposed Architecture
Frontend (Keep TypeScript/Next.js)
├── UI components
├── i18n (9 languages)
├── Voice search
├── Interactive map
└── Deployed on Vercel (static/SSR)
Backend (Migrate to Golang)
├── REST API server (Fiber/Gin)
│ ├── /api/districts
│ ├── /api/compare
│ └── /api/states
│
├── Daily sync service
│ ├── 50 concurrent workers
│ ├── Batch inserts (1000 rows)
│ └── Auto-retry logic
│
└── Deployed on $5 VPS or Fly.io
Database (Same)
└── PostgreSQL (Supabase or self-hosted)
Cache (Same)
└── Redis (Upstash or self-hosted)
Key insight: Keep TypeScript where it shines (frontend), use Golang where performance matters (backend).
Implementation Roadmap
Month 1: Golang API Server
- Replace Next.js API routes with Golang
- Deploy both (Next.js frontend + Golang API)
- Zero downtime migration
Month 2: Golang Sync Service
- Replace TypeScript daily sync script
- Deploy as separate microservice
- 10x faster sync times
Month 3: Optimization
- Horizontal scaling (run multiple instances)
- Advanced caching strategies
- Monitoring (Prometheus + Grafana)
Month 4: Cost Optimization
- Move to self-hosted VPS
- Annual cost: $540 → $60
Why This Matters
This isn't just about performance.
This is about sustainability for a side project that could grow into something real.
The questions I ask myself:
- What if 10,000 users start using this daily?
- What if government wants to partner and needs SLAs?
- What if data grows to 100,000+ districts (all of rural India)?
- What if I want to add real-time updates?
With TypeScript alone: Expensive, slow, hitting limits.
With Golang backend: Cheap, fast, scales horizontally.
Learning Plan (Public Commitment)
I'm learning Golang by building in public:
Week 1-2: Golang basics
- Tour of Go (official tutorial)
- Concurrency patterns (goroutines, channels)
- Standard library exploration
Week 3-4: Build simple API
- Fiber/Gin framework (Express-like)
- Database connections (pgx, GORM)
- API endpoints (REST)
Week 5-6: Build sync service
- Worker pool pattern
- Batch processing
- Error handling & retries
Week 7-8: Deploy to production
- Dockerize
- Deploy to Fly.io (or VPS)
- Monitor performance
Timeline: 2 months to production Golang backend.
The End Goal
A hybrid system:
- TypeScript frontend (React ecosystem, rapid iteration)
- Golang backend (performance, cost-efficiency, scalability)
- Best of both worlds
This is how side projects evolve into production-grade systems.
This is how you build for scale without venture capital.
This is the future of this project.
Stay tuned for the Golang migration series. I'll document everything: wins, losses, and "why did I think this was a good idea" moments. 😅
The Tech Stack (Final)
Frontend:
- Next.js 16 (App Router)
- React 19.2
- TypeScript 5
- TailwindCSS 4
- Framer Motion (animations)
- Fuse.js (search)
- Web Speech API (voice)
- next-intl (i18n)
Backend:
- Next.js API Routes
- PostgreSQL (Supabase)
- Prisma ORM 6
DevOps:
- GitHub Actions (automated sync)
- Vercel (hosting)
- pnpm (package manager)
Data Source:
- data.gov.in API
- Manual CSV imports
Total Cost: $0/month (Free tiers everywhere)
Unknown Chapter: The Map That Nobody Asked For (But I Built Anyway)
Remember that 19MB SVG map that was mocking me?
It's getting a proper funeral. And a rebirth. As an interactive Leaflet map.
The Unnecessary Journey Begins
Rational Brain: "The CSS Grid map works fine. Users can click states. It's functional. Move on."
Developer Brain: "But what if... zoom controls? Tooltips? Choropleth visualization? Fullscreen mode?"
Rational Brain: "That's overkill for employment data."
Developer Brain: "CHALLENGE ACCEPTED."
Why Leaflet? (The Technical Justification)
Current CSS Grid Map:
- ✅ Works
- ✅ Fast
- ✅ Simple
- ❌ No zoom/pan
- ❌ No hover tooltips
- ❌ Not "industry standard"
Leaflet Interactive Map:
- ✅ Zoom/pan controls
- ✅ Rich hover tooltips
- ✅ Choropleth color scales
- ✅ Fullscreen mode
- ✅ Mobile gestures (pinch-to-zoom)
- ✅ Export as image
- ⚠️ Slightly more complex
The Trade-off:
- Complexity: +30%
- User Experience: +200%
- Coolness Factor: +1000%
Decision: Worth it.
The Open Source Moment
I needed GeoJSON files for all 36 Indian states with district boundaries.
Option 1: Process raw shapefiles myself
- Download from government sources
- Convert SHP → GeoJSON
- Simplify coordinates
- Test each file
- Time: 2-3 weeks
Option 2: Search GitHub
- Type: "india geojson districts"
- Find:
udit-001/india-maps-data - Files ready: All 36 states ✅
- Time: 5 minutes
This. This is why I love open source.
Discovering india-maps-data
Repository: https://github.com/udit-001/india-maps-data
What I found:
geojson/
├── india.geojson # All India with state boundaries
└── states/
├── maharashtra.geojson # Districts of Maharashtra
├── tamil-nadu.geojson # Districts of Tamil Nadu
├── uttar-pradesh.geojson # Districts of UP
└── ... (all 36 states!)
CDN URLs (no download needed):
https://cdn.jsdelivr.net/gh/udit-001/india-maps-data@latest/geojson/india.geojson
https://cdn.jsdelivr.net/gh/udit-001/india-maps-data@latest/geojson/states/maharashtra.geojson
My reaction: 🤯
Someone already did the hard work. Clean files. CDN hosted. Free. MIT licensed.
What I saved:
- 2 weeks of map data processing
- Countless hours debugging coordinate systems
- Sanity dealing with QGIS/MapShaper
What I learned: Before building, search GitHub. Seriously. Just search.
The UI Inspiration
While researching Leaflet implementations, I found another gem:
Repository: https://github.com/udit-001/india-maps
An interactive India map built with:
- React + Vite
- Leaflet + react-leaflet
- GeoJSON rendering
- Color customization
- Export functionality
I didn't copy the code. But I studied:
- How they handled GeoJSON loading
- Tooltip implementations
- Color scale logic
- Mobile responsiveness
- State name normalization
Open source isn't just about code. It's about learning patterns, seeing solutions, understanding trade-offs.
The Implementation Plan
Architecture:
/map → All India state-level map
/map/[stateCode] → Individual state district map
Data Flow:
User clicks state on home page
↓
/map (All India Leaflet map)
↓ (clicks Maharashtra)
/map/maharashtra (District-level map)
↓ (clicks district card)
/state/maharashtra (Existing state page)
Tech Stack:
// Dependencies
import { MapContainer, GeoJSON, ZoomControl } from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
// Data
const geoData = await fetch(
'https://cdn.jsdelivr.net/gh/udit-001/india-maps-data@latest/geojson/india.geojson'
).then(r => r.json());
// Visualization
const getColor = (expenditure) => {
if (exp > 1000000000) return '#0f766e'; // High
if (exp > 500000000) return '#14b8a6'; // Medium
if (exp > 100000000) return '#5eead4'; // Low
return '#ccfbf1'; // Very low
};
Features:
-
Choropleth Visualization
- Color states by expenditure
- Dark teal = High, Light teal = Low
- Gray = No data
-
Interactive Tooltips
Maharashtra ━━━━━━━━━━━━━━━━ Districts: 36 Expenditure: ₹2,450.56 Cr Households: 1,23,456 Click to drill down → -
Controls
- Zoom in/out
- Reset view
- Fullscreen toggle
- Metric switcher (expenditure/households/person days)
-
Mobile Gestures
- Pinch to zoom
- Two-finger pan
- Tap for details
The "Is This Overkill?" Moment
The friend in my brain: "So... you already have a working map."
Me: "Yes."
Friend: "And users can click states and navigate."
Me: "Yes."
Friend: "Then why are you rebuilding it with Leaflet?"
Me: "..."
Me: "Because I can?"
Friend: sighs
The Truth:
- Do I need zoom controls for state boundaries? No.
- Do I need choropleth visualization? Not really.
- Do I need hover tooltips? Tables work fine.
But here's the thing:
Side projects aren't about "need." They're about:
- Learning new libraries (Leaflet)
- Solving interesting problems (GeoJSON rendering)
- Building portfolio pieces (impressive demos)
- Having fun (the real reason)
If I only built what was "necessary," I'd never learn anything new.
The Implementation (Week-by-Week)
Hour 1: Basic Setup
- Install
leaflet,react-leaflet - Create
/maproute - Fetch India GeoJSON
- Render basic map
- Result: Blue static map, no interactions
Hour 2: Data Integration
- Connect to PostgreSQL
- Aggregate state-level metrics
- Implement color scale
- Add click handlers
- Result: Colored map with navigation
Hour 3: District Maps
- Create
/map/[stateCode]routes - Fetch state GeoJSON files
- Connect district data
- Add district cards below map
- Result: Full drill-down experience
Hour 4: Polish
- Add tooltips
- Implement legend
- Fullscreen mode
- Mobile optimization
- Result: Production-ready map
Next: Performance
- GeoJSON caching
- Dynamic imports (avoid SSR issues)
- Loading skeletons
- Error boundaries
- Result: Fast, robust, user-friendly
The Challenges (Because Nothing Ever Works First Try)
Challenge 1: "window is not defined"
Error:
ReferenceError: window is not defined
Cause: Leaflet uses browser APIs. Next.js SSR doesn't have window.
Solution:
// Dynamic import with ssr: false
const Map = dynamic(() => import('./map'), {
ssr: false,
loading: () => <MapSkeleton />
});
Challenge 2: GeoJSON State Names Mismatch
GeoJSON: "Andhra Pradesh"
Database: "ANDHRA PRADESH"
Result: States not matching, no data displayed
Solution:
function normalizeStateName(name: string): string {
return name.toUpperCase().trim()
.replace(/&/g, 'AND')
.replace(/\s+/g, ' ');
}
Challenge 3: Mobile Tooltips Not Working
Problem: Hover doesn't work on touch devices
Solution: Detect touch device, use tap instead of hover
const isTouchDevice = 'ontouchstart' in window;
const eventType = isTouchDevice ? 'click' : 'mouseover';
The Performance Numbers
GeoJSON File Sizes:
- All India: 450KB (simplified)
- Maharashtra: 120KB
- Uttar Pradesh: 180KB (most districts)
Load Times:
- Initial map load: ~1.5s (including GeoJSON fetch)
- State map load: ~800ms
- Interactions: 60fps (smooth)
Optimization Techniques:
- GeoJSON Caching: Keep in memory after first load
- Dynamic Imports: Only load Leaflet on map pages
- Coordinate Simplification: Use simplified GeoJSON (10% detail)
- React Query: Cache DB queries for 5 minutes
The Open Source Lesson
Before this project, I thought:
- "I need to build everything from scratch to learn"
- "Using others' work is 'cheating'"
- "Real developers don't use libraries"
Now I know:
- Standing on shoulders of giants is smart, not lazy
- Open source accelerates learning exponentially
- Community contributions make ambitious projects possible
What @udit-001's repositories taught me:
- Someone has solved your problem. Just search.
- Clean data is worth gold. GeoJSON files saved me weeks.
- Code examples teach patterns. I learned Leaflet best practices.
- Give credit. Always acknowledge sources.
- Contribute back. I'll add MGNREGA-specific examples to my repo.
The GitHub Philosophy:
See a problem → Search GitHub → Find solution
↓
Learn from it → Build on it → Share your version
↓
Others find YOUR work → Cycle repeats
This is how the web gets built. One open-source project at a time.
The Unnecessary Features I'm Adding Next
Because why stop now?
- Metric Toggle
- Switch between: Expenditure | Households | Person Days | Works Completed
- Real-time color scale updates
- Time Slider
- Slide through months: April 2024 → October 2025
- Animate employment trends
- Comparison Mode
- Select 2 states
- Side-by-side maps
- Highlight differences
- Export Options
- Download map as PNG
- Export data as CSV
- Generate PDF report
Are these necessary? Not at all.
Will they be cool? Absolutely.
Will I build them? You know it.
The Takeaway
Building this map taught me:
- Overkill is underrated
- Open source is a superpower
- Learning happens when you go beyond "good enough"
- Side projects are for experimentation, not efficiency
If I only built what was "necessary":
- I'd never learn Leaflet
- I'd never discover india-maps-data
- I'd never push my skills further
- This blog post wouldn't exist
The "unnecessary" features are where the real learning happens.
Shoutout & Gratitude
Huge thanks to @udit-001:
- india-maps-data: Clean GeoJSON files for all Indian states
- india-maps: UI reference and implementation patterns
- Saving me from weeks of map data processing hell
Check out his work:
This is the power of open source. One person's weekend project becomes the foundation for someone else's production app.
The Reality Check
Did I Use LLMs?
Yes. A lot.
- Initial code scaffolding
- Prisma schema design help
- Translation assistance
- Debugging weird TypeScript errors
- CSS Grid layout calculations
Did I "Vibe-Code" Everything?
Hell no.
Here's the truth:
- LLMs are tools, not magic wands
- You need to understand your stack
- You need to review all generated code
- You need to test everything
- You need to debug when things break (and they will)
What LLMs CAN'T do:
- Understand your project's architecture
- Make design decisions for you
- Debug production issues (without context)
- Optimize performance
- Handle edge cases
What I DID:
- Designed database schema
- Architected sync system
- Chose tech stack
- Made UX decisions
- Debugged all issues
- Wrote documentation
- Tested on real devices
LLMs helped me code faster. They didn't code FOR me.
The Numbers (Final)
Data Coverage
- States: 36 states and UTs
- Districts: 740 (Maharashtra: 34 complete)
- Records: 646+ monthly metrics (Maharashtra)
- Time Range: April 2024 - October 2025
- Data Points: 35+ metrics per record
Technical Metrics
- Lines of Code: ~15,000 (excluding dependencies)
- Components: 40+ Shadcn UI components
- API Endpoints: 6 REST APIs
- Languages: 9 (3 production, 6 beta)
- Translation Keys: 225+ total
- Bundle Size: 150KB gzipped (first load)
- Build Time: ~90 seconds
- TypeScript: 100% coverage
Performance
- Lighthouse Score: 90+ (target)
- First Contentful Paint: < 1.5s
- Time to Interactive: < 3s
- API Response: < 500ms (uncached)
- Daily Sync: 15-60 minutes (automated)
What I Learned
1. Assignments Can Evolve
Don't limit yourself to the brief. If you see potential, explore it.
2. Free > Paid (When Possible)
GitHub Actions > Vercel Cron Jobs (for my use case)
3. Simple > Complex (When It Works)
CSS Grid > 210KB SVG Map (for performance)
4. Automation > Manual Labor
15-60 min automated sync > Manual CSV updates forever
5. LLMs Are Tools, Not Gods
Use them to speed up, not replace thinking.
6. Real Users Have Real Needs
Voice input, multi-language support, mobile optimization — these aren't "nice-to-haves".
What's Next?
Short-term
- ✅ Complete Maharashtra (Done)
- ⏳ Optimize SVG map (still in repo, waiting for redemption)
- ⏳ Add 5 more states (UP, Bihar, Karnataka, TN, Rajasthan)
- ⏳ District detail pages with charts
- ⏳ PWA support (offline mode)
Mid-term
- ⏳ All 36 states/UTs
- ⏳ ML-based insights
- ⏳ Export data (CSV, PDF)
- ⏳ Mobile app (React Native)
Long-term
- ⏳ Real-time updates
- ⏳ AI chatbot
- ⏳ Predictive analytics
- ⏳ Government partnership (fingers crossed)
Try It Yourself
Live Demo: MGNREGA INDIA
GitHub: github.com/certainlyMohneeesh/mnrega-mah
Want to contribute?
- Optimize the SVG map (please, I'm begging)
- Add more language translations
- Report bugs
- Suggest features
Final Thoughts
This started as a 2-week assignment. It's been 3+ months.
I fought:
- Infinite fetch loops
- Vercel timeouts
- Laggy SVG maps
- CSV hell
- Daily data sync nightmares
I learned:
- GitHub Actions are underrated
- CSS Grid is underrated
- Free tiers are your friend
- LLMs are powerful tools (not magic)
- Real users need accessible UIs
Would I do it again?
Yes. In a heartbeat.
Because assignments that turn into real projects? Those are the ones that matter.
Let's Connect
GitHub: @certainlyMohneeesh
LinkedIn: linkedin.com/in/mohneeesh
Email: certainlymohneesh@gmail.com
P.S. If you know how to make a 19MB GeoJSON SVG map NOT lag on mobile, seriously, DM me. That code is still sitting in interactive-india-map-v3.tsx, mocking me. 😅
Comments
No comments yet.
Sign in to comment.