Web app and PWA for displaying Underfoot geologic and hydrologic maps, available at underfoot.rocks.
Underfoot is an offline-first app for viewing geologic and hydrologic maps. Map data is distributed as downloadable "packs" that get stored in the browser, so the app keeps working without a network connection.
- React 18 and TypeScript, bundled with Vite 5
- MapLibre GL JS for map rendering, with PMTiles vector tiles
- MUI (Material UI) 5 with Emotion for styling
- Zustand for state and localForage for offline (IndexedDB) storage
- vite-plugin-pwa for the service worker and web app manifest
- Node.js 20 or newer (Vite 5 also runs on 18, but 20+ is recommended)
- npm 10 or newer, which ships with Node 20
git clone git@github.com:kueda/underfoot-web.git
cd underfoot-web
npm installnpm install runs husky through the prepare script to install the Git hooks (see
Git hooks below).
npm run devThis starts Vite at https://localhost:5173. The dev server uses HTTPS via
@vitejs/plugin-basic-ssl because
geolocation and service workers require a secure context. The certificate is self-signed, so
the browser will warn on first load; accept it to continue.
vite-plugin-pwa has devOptions.enabled set, so a service worker is built in development
too. Its output lands in dev-dist/ (git-ignored, regenerated on every run). If the app
serves stale assets during development, unregister the service worker and clear site data in
the browser's dev tools.
The app fetches its pack manifest and map data from https://static.underfoot.rocks at
runtime. Nothing needs to be configured or downloaded locally, but a network connection is
required the first time a pack is loaded. There are no environment variables.
The data behind those packs is produced by a separate project, kueda/underfoot.
| Command | Description |
|---|---|
npm run dev |
Start the Vite dev server on https://localhost:5173 |
npm run build |
Type-check with tsc, then build the production bundle to dist/ |
npm run preview |
Serve the built dist/ locally |
npm run lint |
Run ESLint over src (.ts/.tsx); zero warnings allowed |
There is no automated test suite yet.
Husky installs a pre-commit hook that:
- runs
lint-staged, which applieseslint --fixto staged.ts/.tsxfiles - runs
tsc --noEmitover the whole project
A commit fails if either step fails.
The post-commit and prepare-commit-msg hooks are dispatchers: each runs the shared steps
in the tracked .husky/<hook> file, then sources an optional per-developer
.husky/<hook>.local file if present. The .local files are git-ignored (via *.local), so
you can add personal automation without touching what everyone else runs.
Run npm run lint and npm run build after any TypeScript change; both must pass before a
change is considered done. npm run build runs tsc first, so it also surfaces type errors.
Pushing to main triggers the .github/workflows/vite-github-pages-deploy.yml workflow,
which builds the app and deploys it to GitHub Pages. The CNAME file points the Pages site
at the custom domain underfoot.rocks.