π TrainTracker
Live tracking North American intercity passenger rail β πΊπΈ Amtrak, π¨π¦ VIA Rail, π΄ Brightline. Check train schedules with a live-updating timeline, and visualize realtime positions on an interactive map.
- π Train search, filter, and sort
- π Live train positions displayed on a map, extrapolated between GPS updates
- π Per-stop push notifications for arrivals and departures
- π Light/dark mode, configurable units and timezone display
- π± Desktop- and mobile-optimized UIs
npm install
npm run devOpens at http://localhost:3000.
Copy .env.example to .env and fill in your VAPID keys (required for push notifications):
cp .env.example .envGenerate VAPID keys with:
npx web-push generate-vapid-keys| Command | Description |
|---|---|
npm run dev |
Start the development server |
npm run build |
Build for production |
npm start |
Run the production build |
npm run lint |
Lint and format check |
npx tsc --noEmit |
Type check |
- API layer (
app/api/trains/) β Fetches and decrypts train data from Amtrak, VIA Rail, and Brightline data providers - Train provider (
app/providers/train.tsx) β Polls the API seconds and distributes data via React context - Track snapping (
app/components/Map/calc.ts) β Snaps GPS coordinates to the nearest point on track geometries using Turf.js; extrapolates position between updates based on timetable - Map rendering (
app/components/Map/) β Displays trains on a MapLibre GL map viareact-map-glwith custom markers, labels, and route styling
app/
βββ api/ # Next.js API routes (trains, notifications)
βββ components/ # React components (Map, timeline, notifications)
β βββ Map/ # Map system: rendering, track snapping, display logic
βββ providers/ # React context providers (train data, settings)
βββ lib/ # Shared utilities (Prisma client, notification polling)
βββ types.ts # Shared TypeScript types
public/
βββ map_data/ # GeoJSON track geometries
db/
βββ schema.prisma # Database schema
βββ migrations/ # Prisma migrations
On startup, app/lib/gtfs-import.ts downloads and parses GTFS feeds for each agency, then:
- Imports stops and trips into the SQLite database (used at runtime for station lookups and track shape resolution)
- Generates
public/map_data/track.jsonβ the GeoJSON LineString features used to render tracks on the map
Results are cached for 24 hours (tracked via GtfsImportMeta) so subsequent restarts skip the download. In production the filesystem is read-only, so the GeoJSON is pre-built during the Docker image build and only the database upsert runs at startup.
Uses SQLite via Prisma. The database file (db/app.db) is created automatically on first run; migrations run on startup. The schema has four models:
| Model | Purpose |
|---|---|
PushSubscription |
Web push credentials, plus the train, stop, and notification type a user subscribed to |
GtfsStop |
Station records from GTFS feeds (code, name, coordinates, timezone) |
GtfsTrip |
Trip records linking train numbers to GTFS shape IDs |
GtfsImportMeta |
Singleton that records the last successful import time for the 24-hour cache |
A server-side background poller (app/lib/notifications.ts) checks train status every 30 seconds and sends web push notifications 5 minutes before a subscribed arrival or departure. A service worker (public/service-worker.js) handles delivery in the browser background.
See DEPLOYMENT.md for instructions on deploying to a VPS with Docker and nginx.