TL;DR
I built a production-ready disaster monitoring app in 30 days using vanilla JavaScript. Here's what I learned about performance, API optimization, and shipping real products.
π Live Demo | Source Code
Table of Contents
- Why I Built This
- Tech Stack & Architecture
- Key Features
- Technical Challenges
- Performance Optimization
- Lessons Learned
- What's Next
Why I Built This {#why}
Three months into learning JavaScript, I hit tutorial hell. I could follow along, but couldn't build anything independently.
DisasterWatch was my "sink or swim" project. Here's why I chose it:
- Real data - Working with live APIs (USGS, Open-Meteo)
- Complex state - Managing filters, pagination, caching
- Performance challenges - Rendering 10,000+ data points
- Social impact - Helping communities stay informed
Tech Stack & Architecture {#tech-stack}
Zero dependencies. Just vanilla JavaScript, HTML5, and CSS3.
Why no frameworks?
- Master the fundamentals first
- Understand what frameworks abstract away
- Smaller bundle size (45KB total)
- Faster load times
File Structure
disaster-dashboard/
βββ index.html # Semantic HTML5
βββ css/
β βββ style.css # CSS Custom Properties + Grid
βββ js/
β βββ config.js # API endpoints & constants
β βββ utils.js # 30+ helper functions
β βββ api.js # Data fetching & caching
β βββ app.js # State management + UI
Why this structure?
Separation of concerns. Each file has one responsibility:
-
config.js- Configuration (what) -
utils.js- Utilities (how) -
api.js- Data layer (fetch) -
app.js- Application logic (orchestrate)
Key Features {#features}
1. Real-Time Data
// Fetch earthquakes with caching
async function fetchEarthquakes(timeframe = 'day') {
const cacheKey = `earthquakes_${timeframe}`;
// Check cache first
const cached = cache.get(cacheKey);
if (cached) return cached;
// Fetch from API
const data = await fetch(EARTHQUAKE_API_URL);
const earthquakes = await data.json();
// Cache for 5 minutes
cache.set(cacheKey, earthquakes, 300000);
return earthquakes;
}
Why caching matters:
- First load: 500ms (network request)
- Cached load: 5ms (memory read)
- 100x faster!
2. Smart Filtering
Users can filter by:
- Magnitude (2.5+, 4.0+, 5.0+, etc.)
- Time period (hour, day, week, month)
- Location (search by place name)
- Proximity (earthquakes near user)
3. Performance Optimization
The app handles 10,000+ earthquakes without lag.
How?
// Pagination instead of rendering everything
const itemsPerPage = 20;
const displayedItems = earthquakes.slice(0, itemsPerPage);
// Load more on button click
function loadMore() {
currentPage++;
const start = currentPage * itemsPerPage;
const end = start + itemsPerPage;
const moreItems = earthquakes.slice(start, end);
render(moreItems);
}
Result: 0.9s initial load vs 8.2s rendering everything.
4. Accessibility
- β WCAG 2.1 compliant
- β Keyboard navigation
- β Screen reader support
- β Proper ARIA labels
- β Focus management
Example:
<button
aria-label="Toggle dark mode"
aria-pressed="false"
id="theme-toggle">
<i class="fas fa-moon" aria-hidden="true"></i>
</button>
Technical Challenges & Solutions {#challenges}
Challenge 1: API Rate Limiting
Problem: Free tier = 60 requests/hour
Solution: Implement caching layer
const cache = new Map();
function set(key, value, ttl) {
cache.set(key, {
data: value,
expires: Date.now() + ttl
});
}
function get(key) {
const item = cache.get(key);
if (!item) return null;
// Check if expired
if (Date.now() > item.expires) {
cache.delete(key);
return null;
}
return item.data;
}
Impact: Reduced API calls by 85%
Challenge 2: Search Performance
Problem: Search fired on every keystroke = 10 calls per word
Solution: Debouncing
function debounce(func, delay) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), delay);
};
}
// Usage
const debouncedSearch = debounce(handleSearch, 500);
searchInput.addEventListener('input', debouncedSearch);
Impact: 90% fewer API calls
Challenge 3: Mobile Performance
Problem: Large datasets crashed mobile browsers
Solution: Progressive enhancement
- Desktop: Show 50 items initially
- Mobile: Show 20 items initially
- Use
matchMediato detect device
const isMobile = window.matchMedia('(max-width: 768px)').matches;
const itemsToShow = isMobile ? 20 : 50;
Performance Metrics {#performance}
Before Optimization:
- First Contentful Paint: 3.2s
- Time to Interactive: 8.1s
- Lighthouse Score: 62
After Optimization:
- First Contentful Paint: 0.8s β‘
- Time to Interactive: 1.4s β‘
- Lighthouse Score: 95 π―
Key optimizations:
- API caching
- Debounced search
- Pagination
- DOM element caching
- Lazy loading images
- Minified CSS/JS
Lessons Learned {#lessons}
1. Start Small, Iterate Fast
Don't build everything at once.
My approach:
- Week 1: Basic UI + API connection
- Week 2: Filtering & search
- Week 3: Performance optimization
- Week 4: Polish & deploy
2. Test on Real Devices
Chrome DevTools mobile emulator β Real iPhone
Issues I found only on real devices:
- Touch target too small
- Text too tiny
- API too slow on 3G
3. Accessibility from Day 1
Don't bolt it on at the end. Build it in.
Easy wins:
- Semantic HTML (
<header>,<main>,<nav>) - Alt text on images
- ARIA labels on buttons
- Keyboard navigation
4. Read Documentation
MDN > Stack Overflow > Random blogs
When stuck:
- Read error message carefully
- Check MDN docs
- Console.log everything
- Then Google specific error
5. Ship Imperfect
Perfect is the enemy of done.
I wanted to add:
- Interactive map (Leaflet.js)
- Historical charts
- Email alerts
- PWA support
But I shipped with core features. Rest can come later.
What's Next {#next}
Short-term:
- [ ] Add interactive map
- [ ] Implement service workers (PWA)
- [ ] Add Chart.js visualizations
- [ ] Multi-language support
Long-term:
- [ ] Rebuild with React (for learning)
- [ ] Add Node.js backend
- [ ] MongoDB for historical data
- [ ] Real-time WebSocket updates
Final Thoughts
Three months ago, I couldn't build a working button.
Today, I shipped a production app that:
- Handles 10,000+ data points
- Loads in under 1 second
- Works on all devices
- Helps communities stay safe
You can too.
Start with something that scares you.
Break it into tiny pieces.
Ship something imperfect.
Iterate.
That's how you learn.
Let's Connect!
I'm looking for junior developer roles or internships!
Found this helpful? Share it with someone learning web development! π
Tags: #javascript #webdev #beginners #tutorial #webperformance #learntocode





Top comments (0)