Skip to main content
COSMICBYTEZLABS
NewsSecurityHOWTOsToolsStudyTraining
ProjectsNewsletterHire MeAbout
Subscribe

Press Enter to search or Esc to close

News
Security
HOWTOs
Tools
Study
Training
Projects
Newsletter
Hire Me
About
RSS Feed
Reading List
Subscribe

Stay in the Loop

Get the latest security alerts, tutorials, and tech insights delivered to your inbox.

Subscribe NowFree forever. No spam.
COSMICBYTEZLABS

Your trusted source for IT intelligence, cybersecurity insights, and hands-on technical guides.

1184+ Articles
136+ Guides

CONTENT

  • Latest News
  • Security Alerts
  • HOWTOs
  • Checklists
  • Projects
  • Exam Prep

RESOURCES

  • Search
  • Browse Tags
  • Newsletter Archive
  • Reading List
  • RSS Feed

COMPANY

  • About Us
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CosmicBytez Labs. All rights reserved.

System Status: Operational
  1. Home
  2. Projects
  3. Orbit: An Offline-First Fitness PWA with Dual Storage Backend
Orbit: An Offline-First Fitness PWA with Dual Storage Backend
PROJECTIntermediate

Orbit: An Offline-First Fitness PWA with Dual Storage Backend

A household fitness PWA with weight tracking, meal planning, workouts, and pantry — built on Next.js 15 with a single codebase that runs against either Supabase or local SQLite, plus an IndexedDB mutation queue for offline.

Dylan H.

Projects

May 5, 2026
3 min read
4-6 hours

Tools & Technologies

Next.js 15Supabasebetter-sqlite3React QueryPlaywright

Overview

Most fitness apps want your data in their cloud. Orbit is the opposite — a personal fitness PWA where the same codebase drives a Supabase-backed cloud deployment and a Docker self-host with local SQLite, with no per-environment branching above the data layer. This writeup covers the dual-storage abstraction, the offline mutation queue, and the provider stack that holds the app together.

Stack

  • Framework: Next.js 15.5, React 18, TypeScript 5.7 (strict)
  • Styling: Tailwind CSS 3.4
  • Database: Supabase 2.89 (cloud) or SQLite via better-sqlite3 (local)
  • Data Fetching: @tanstack/react-query 5.90
  • Testing: Jest 30 + React Testing Library, Playwright 1.57
  • Analytics: Vercel Analytics + Speed Insights
  • Deployment: Vercel

Key Features

  • Weight tracking with trend visualization
  • Meal planning and food logging (OpenFoodFacts integration)
  • Workout logging with 30+ pre-built routines
  • Recipe browser with create/edit and CSV import
  • Pantry inventory management
  • Household multi-person support
  • Demo mode (works without authentication)
  • Offline-first with an IndexedDB mutation queue
  • PWA with service worker

Provider Stack

ThemeProvider
  → QueryProvider
    → AuthProvider
      → CSRFProvider
        → PersonProvider
          → ToastProvider
            → ConnectionStatusProvider
              → AppInitializer

Three Auth Paths (PersonProvider)

  1. Authenticated: loads from API/Supabase. Retries after 500ms if empty (session timing).
  2. Demo Mode: uses DEMO_PERSONS from demo-data.ts. Full functionality, localStorage-backed.
  3. Non-Authenticated: tries localStorage → API → falls back to demo data.

Offline Support

offline-queue.ts is an IndexedDB-backed mutation queue. Mutations are auto-queued on network failure and auto-replayed when the connection returns. Only 5xx and network errors retry — 4xx is treated as a permanent client error and surfaced.

Dual Database Backend

A single codebase supports both Supabase and SQLite by aliasing fields at the data layer:

ConceptSQLiteSupabase
Pantry itemitemname
Recipe prep timeprep_time_minprep_time_minutes
Nutritiontop-level fieldsnested nutrition object

Hooks call isSQLiteMode() (driven by an /api/config endpoint exposing a sqliteEnabled flag) and branch between row mappers. The UI layer never knows which backend is live.

Environment Variables

VariablePurpose
NEXT_PUBLIC_SUPABASE_URLSupabase project URL (Supabase mode)
NEXT_PUBLIC_SUPABASE_ANON_KEYSupabase anon key (Supabase mode)
DATABASE_TYPESet to sqlite to use the SQLite backend
DATABASE_PATHSQLite database location
DEMO_MODEAllow unauthenticated access

The app runs without Supabase vars — it falls back to SQLite or demo mode.

Lessons Learned

  • Supabase navigator.locks deadlock: Supabase v2.39+ uses navigator.locks in getSession(). If a tab crashes while holding the lock, every other tab hangs indefinitely. The fix is a custom lock wrapper in lib/supabase.ts with a 5-second timeout. Diagnose by running navigator.locks.query() in the console.
  • Empty load after login: loadPersons() sometimes returns empty immediately after auth — the session token hasn't propagated yet. Auto-retry after a 500ms delay.
  • CSRF flow: server generates a token → HttpOnly cookie → client sends it back via X-CSRF-Token header → middleware validates on mutations only. Wrappers authFetch() and csrfFetch() keep call sites simple.

Testing

194 unit tests (Jest + React Testing Library) plus Playwright E2E. CI runs lint → test → build on every PR.

#Next.js#PWA#Supabase#SQLite#Offline-First#TypeScript#React Query

Related Articles

Subscription Tracker: A 2-Tier Storage PWA for Recurring Expenses

Track subscriptions, utility bills, and payments with auto-payment generation and analytics — one Next.js codebase that runs on Docker with SQLite or on Vercel with localStorage, no per-target branching above the data layer.

3 min read

Self-Hosted IPTV Manager with HDHomeRun Emulation for Plex

An IPTV stream manager that pretends to be an HDHomeRun tuner so Plex DVR can record over-the-air-style live TV — M3U import, tier-based channel classification, FFmpeg HLS remux, and an XMLTV EPG.

3 min read

A Real-Time Trading Dashboard with Next.js 15 and lightweight-charts

Frontend for an algorithmic trading bot — TradingView-style candlestick charts, WebSocket auto-reconnect, dark theme, and zero SSR because the bot's API lives on a separate server.

3 min read
Back to all Projects