FitPal — Offline Fitness Tracker

Problem:
Tracking workouts and nutrition usually requires heavy apps with accounts, ads, and cloud dependencies.
Solution:
Built a professional-grade, zero-dependency PWA fitness dashboard that works entirely offline using localStorage.
Impact:
100% private, instant-load fitness tracking with comprehensive analytics, diet plans, and progressive overload tracking.
Overview
FitPal is a professional-grade, zero-dependency PWA fitness dashboard built for serious athletes. It is contained entirely within a single HTML file, requiring no accounts, no cloud, and loading instantly. It supports 6-day PPL splits, drop sets, analytics heatmaps, nutrition tracking, and full data export/import via JSON.
Results & metrics
- 100% private fitness and workout logging with zero cloud storage dependencies
- Instantaneous sub-100ms app loading through Service Worker asset caching
- Zero-dependency single-file architecture runnable on any modern web browser
- Complete JSON data export and import for seamless multi-device backups
Before / after
Data Privacy
Before: Data stored on external servers with required accounts
App Loading
Before: Network dependent loading times
Connectivity Dependency
Before: App completely unusable in gyms with poor cellular reception
What I built
Local-only storage
Workouts and nutrition stay in the browser's localStorage: no accounts, no cloud.
Works offline
A service worker loads pages network-first and assets cache-first, with offline fallback.
Stack
- Vanilla JavaScript, ES2022
- CSS3, Custom Properties, Grid, Flexbox
- PWA, Service Worker, Manifest
- Netlify Deployment, with strict security headers
Decisions
Key trade-offs and design calls that shaped the final delivery.
Zero-dependency single file
Why
- Context
- Wanted maximum simplicity, performance, and portability.
- Decision
- Built the entire application in vanilla HTML/CSS/JS without frameworks to ensure instant load and easy maintenance.
localStorage for persistence
Why
- Context
- Privacy and offline usage were top priorities.
- Decision
- Avoided any backend database or accounts, storing all workout and nutrition data strictly in the browser's localStorage.
Architecture
The primary system boundaries, runtime pieces, and how the project was structured in production.
- Vanilla HTML5 / CSS3 / JS UI ↔ Service Worker
- Service Worker → App Cache & Static Shell (Cache-First)
- Service Worker → Netlify CDN with Strict CSP (Network-First)
- Vanilla HTML5 / CSS3 / JS UI ↔ Encrypted localStorage (Workout & Nutrition Data)
- Vanilla HTML5 / CSS3 / JS UI ↔ JSON Backup Export & Import
- Vanilla JS & localStorage
Frontend & Persistence
How
100% private, local-only data persistence with no cloud dependencies or accounts.
- Service Worker
Offline Capability
How
Network-first for navigation, cache-first for assets, providing robust offline fallback.
- Netlify & Security Headers
Security & Deployment
How
Strict-Transport-Security, Content-Security-Policy, and X-Frame-Options configured via netlify.toml.
Mermaid source
Paste into mermaid.live to edit the diagram.
flowchart TD
subgraph Browser [Client Browser Layer]
UI[Vanilla HTML5 / CSS3 / JS UI]
EXP[JSON Backup Export & Import]
end
subgraph Worker [PWA Offline Engine]
SW[Service Worker]
CACHE[(App Cache & Static Shell)]
end
subgraph Persistence [Private Device Storage]
LS[(Encrypted localStorage)]
end
subgraph Edge [Static Edge Delivery]
NET[Netlify CDN with Strict CSP]
end
UI <--> SW
SW -->|Cache-First| CACHE
SW -->|Network-First| NET
UI <-->|Workout & Nutrition Data| LS
UI <--> EXPPipeline
How changes moved from development through validation and deployment.
Single file structure utilizing native ES modules and CSS variables without bundling
100/100 PWA score with strict offline manifest validation and responsive checks
Automated deployment with strict Content-Security-Policy and HSTS headers
Select a step for its detail
Incidents
Operational failures, rehearsals, or recovery moments that changed how the system was run.
- Medium
Service Worker cache staleness on application update
Resolution
Implemented skipWaiting and clients.claim lifecycle hooks with versioned cache naming
Lesson: PWA service workers require explicit cache invalidation logic to deliver app shell updates immediately
- Low
localStorage quota saturation during extensive history logging
Resolution
Added automated JSON backup export prompts and efficient binary payload serialization
Lesson: Always design client-side persistent apps with data portability and quota warnings