Tools
I Built a Website Tracker This Weekend — Here's What I Learned
2025-12-31
0 views
admin
The Problem ## The Solution: SiteOps ## Tech Stack ## Key Features ## 1. Real-Time Status Monitoring ## 2. Visit Tracking & Analytics ## 3. Comprehensive Analytics Dashboard ## 4. Local-First Architecture ## Challenges & Learnings ## Challenge 1: CORS for Status Checking ## Challenge 2: Responsive Design Without Frameworks ## Challenge 3: Data Persistence ## Challenge 4: Real-Time Updates ## Design Decisions ## Why Vanilla JavaScript? ## Why localStorage? ## Why Chart.js? ## Features That Make It Special ## What I'd Do Differently ## Try It Out ## Open Source ## Final Thoughts As a developer juggling multiple client websites, I was frustrated with traditional bookmark managers. They don't track visits, show status, or provide analytics. So I built SiteOps — a clean, local-first website monitoring dashboard. Existing tools were either too complex, required sign-ups, or lacked the features I needed. SiteOps is a vanilla JavaScript web app that runs entirely in your browser. All data stays local using localStorage — no backend, no database, no accounts required. Live Demo: https://siteops-io.netlify.app
Source Code: https://github.com/inkFusionLabs/siteops Each website card shows live status (online/offline) with visual indicators: Tracks every visit with timestamps, device types, and patterns: Built with Chart.js to visualize: All data persists in localStorage: Problem: Can't directly fetch websites due to CORS restrictions. Solution: Used a CORS proxy service (allorigins.win) for status checks. Not perfect, but works for basic monitoring. Learning: Browser security is strict for good reasons, but it makes client-side monitoring tricky. Problem: Making a complex dashboard responsive with pure CSS. Solution: CSS Grid with repeat(auto-fit, minmax()) for flexible layouts: Learning: Modern CSS Grid is powerful enough for most layouts without frameworks. Problem: Ensuring data survives browser restarts. Solution: localStorage with automatic save on every action. Added export/import for backup. Learning: localStorage is reliable but has size limits (~5-10MB). For larger datasets, IndexedDB would be better. Problem: Updating UI without page refresh. Solution: Event-driven architecture with manual DOM updates: Learning: Vanilla JS can handle complex UIs, but state management gets tricky at scale. Visit the live demo and: All your data stays in your browser — completely private and local. SiteOps is open source and available on GitHub. Contributions welcome! Building SiteOps taught me: If you're tracking multiple websites, give SiteOps a try. And if you have feedback or suggestions, I'd love to hear them! Tags: #javascript #webdev #productivity #opensource #pwa #localstorage #analytics #webdevelopment Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK:
async checkWebsiteStatus(url) { try { const proxyUrl = `https://api.allorigins.win/get?url=${encodeURIComponent(url)}`; const response = await fetch(proxyUrl, { method: 'GET', signal: controller.signal }); return response.ok ? 'online' : 'offline'; } catch (error) { return 'offline'; }
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
async checkWebsiteStatus(url) { try { const proxyUrl = `https://api.allorigins.win/get?url=${encodeURIComponent(url)}`; const response = await fetch(proxyUrl, { method: 'GET', signal: controller.signal }); return response.ok ? 'online' : 'offline'; } catch (error) { return 'offline'; }
} CODE_BLOCK:
async checkWebsiteStatus(url) { try { const proxyUrl = `https://api.allorigins.win/get?url=${encodeURIComponent(url)}`; const response = await fetch(proxyUrl, { method: 'GET', signal: controller.signal }); return response.ok ? 'online' : 'offline'; } catch (error) { return 'offline'; }
} COMMAND_BLOCK:
visitWebsite(url) { const website = this.websites.find(w => w.url === url); if (website) { website.visits = (website.visits || 0) + 1; website.lastVisited = new Date().toISOString(); // Track device type website.visitHistory.push({ timestamp: new Date().toISOString(), deviceType: this.getDeviceType(), hour: new Date().getHours() }); this.saveWebsites(); } window.open(url, '_blank');
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
visitWebsite(url) { const website = this.websites.find(w => w.url === url); if (website) { website.visits = (website.visits || 0) + 1; website.lastVisited = new Date().toISOString(); // Track device type website.visitHistory.push({ timestamp: new Date().toISOString(), deviceType: this.getDeviceType(), hour: new Date().getHours() }); this.saveWebsites(); } window.open(url, '_blank');
} COMMAND_BLOCK:
visitWebsite(url) { const website = this.websites.find(w => w.url === url); if (website) { website.visits = (website.visits || 0) + 1; website.lastVisited = new Date().toISOString(); // Track device type website.visitHistory.push({ timestamp: new Date().toISOString(), deviceType: this.getDeviceType(), hour: new Date().getHours() }); this.saveWebsites(); } window.open(url, '_blank');
} CODE_BLOCK:
saveWebsites() { localStorage.setItem('websiteTracker', JSON.stringify(this.websites));
} loadWebsites() { const stored = localStorage.getItem('websiteTracker'); return stored ? JSON.parse(stored) : [];
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
saveWebsites() { localStorage.setItem('websiteTracker', JSON.stringify(this.websites));
} loadWebsites() { const stored = localStorage.getItem('websiteTracker'); return stored ? JSON.parse(stored) : [];
} CODE_BLOCK:
saveWebsites() { localStorage.setItem('websiteTracker', JSON.stringify(this.websites));
} loadWebsites() { const stored = localStorage.getItem('websiteTracker'); return stored ? JSON.parse(stored) : [];
} CODE_BLOCK:
const proxyUrl = `https://api.allorigins.win/get?url=${encodeURIComponent(url)}`; Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
const proxyUrl = `https://api.allorigins.win/get?url=${encodeURIComponent(url)}`; CODE_BLOCK:
const proxyUrl = `https://api.allorigins.win/get?url=${encodeURIComponent(url)}`; CODE_BLOCK:
.websites-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 20px;
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
.websites-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 20px;
} CODE_BLOCK:
.websites-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 20px;
} CODE_BLOCK:
renderWebsites() { const filtered = this.getFilteredWebsites(); grid.innerHTML = filtered.map(website => this.createWebsiteCard(website) ).join('');
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
renderWebsites() { const filtered = this.getFilteredWebsites(); grid.innerHTML = filtered.map(website => this.createWebsiteCard(website) ).join('');
} CODE_BLOCK:
renderWebsites() { const filtered = this.getFilteredWebsites(); grid.innerHTML = filtered.map(website => this.createWebsiteCard(website) ).join('');
} - Track which sites I visit most
- Monitor site status (up/down)
- Organize sites by project/client
- See usage patterns and analytics
- Keep everything local (no cloud, no accounts) - Vanilla HTML/CSS/JavaScript — No frameworks, no build process
- Chart.js — For analytics visualizations
- localStorage — Client-side data persistence
- PWA-ready — Installable as a web app - Visit trends over 30 days
- Category breakdowns
- Device usage (desktop/mobile/tablet)
- Most visited websites
- Peak usage hours and days - Simplicity: No build process, no dependencies
- Performance: Fast load times, minimal bundle size
- Learning: Better understanding of fundamentals
- Portability: Works anywhere HTML/CSS/JS works - Privacy: Data never leaves the browser
- Simplicity: No backend infrastructure needed
- Speed: Instant saves/loads
- Offline: Works without internet after first load - Easy integration: CDN, no build step
- Good defaults: Looks professional out of the box
- Flexible: Customizable when needed - 5-Star Rating System — Rate websites for quick reference
- Keyboard Shortcuts — Power-user friendly (Ctrl+N, Ctrl+F, etc.)
- Export/Import — Backup your data as JSON
- Device Tracking — See if you access sites from desktop/mobile/tablet
- Return Visit Rate — Analytics on repeat visits
- Glassmorphism UI — Modern, clean design - IndexedDB — For larger datasets and better performance
- Service Worker — For true offline functionality
- WebSockets — For real-time status updates (if I added a backend)
- Testing — Add unit tests for core functionality
- TypeScript — For better code maintainability - Add a few websites
- Visit them a few times
- Check out the analytics dashboard
- Export your data - Vanilla JS can build complex apps
- localStorage is powerful for local-first apps
- Good UX matters more than frameworks
- Sometimes the best solution is the simplest one
how-totutorialguidedev.toaimljavascriptdatabasegitgithub