A real-time 3D satellite tracker rendered on an interactive WebGL globe. SATMAP7 pulls live Two-Line Element (TLE) data from CelesTrak, propagates thousands of satellite positions client-side using the SGP4 algorithm, and renders them across 7 color-coded constellations on a photorealistic Earth — all at 60 fps.
Table of Contents
SATMAP7 is a full-stack Next.js application that visualizes Earth's orbital environment in the browser. The server fetches TLE data from CelesTrak, normalizes and caches it through a three-layer caching system (Next.js fetch revalidation → Upstash Redis → in-memory fallback), then exposes it via a set of typed API routes. The client receives satellite records, runs SGP4 propagation on every animation frame, and maps each satellite's ECI coordinates into Three.js scene space — producing a live, physically-grounded view of what is actually orbiting overhead.
The application has 6 internal abstraction layers — UI/HUD, State, Graphics, Controller, Data, and Types — documented in the in-app /docs page.
- Live SGP4 Propagation — Client-side orbital mechanics via
satellite.js. Every animation frame recomputes positions from TLE data and maps ECI → scene coordinates with Earth-rotation correction via GMST. - 7 Color-Coded Constellations — Starlink, OneWeb, Space Stations, Weather, Navigation/GPS, Debris, and Other Active satellites, each with an individually configurable visibility toggle.
- Orbit Trail Rendering — Four trail modes per selected satellite:
None,History(last 90 min),Full Orbit(one complete period ahead), andBoth. Trails are computed as polyline point arrays sampled every 30 s (history) or 60 s (full orbit). - Camera Fly-To Animation — Selecting any satellite triggers an interpolated camera reposition that smoothly centers the view onto the target. User interaction preempts the animation at any point.
- Satellite Info + Media Panel — Click any point to see NORAD ID, inclination, eccentricity, altitude, velocity, orbit class (LEO/MEO/GEO/HEO), and a constellation image sourced from Wikimedia.
- Search by Name or NORAD ID — Debounced search bar backed by a server-side
/api/searchroute. Results cached in Redis for 5 minutes. - Responsive HUD — Desktop shows a persistent double-panel layout (Stats Bar + Category Sidebar). Mobile/tablet switches to a compact toolbox-kit overlay to maximize globe visibility.
- Three-Layer Caching — CelesTrak is hit at most once per category per 30 minutes. Redis provides distributed persistence; an in-memory dictionary handles dev environments without Redis configured.
- 6 000-Star Starfield — GPU-friendly instanced particle background rendered in R3F.
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router) |
| UI Runtime | React 19 |
| 3D / WebGL | @react-three/fiber, @react-three/drei, three r183 |
| Orbital Mechanics | satellite.js (SGP4/SDP4) |
| State Management | Zustand 5 with subscribeWithSelector |
| Styling | Tailwind CSS 4, tailwind-scrollbar |
| Caching | Upstash Redis (@upstash/redis) + in-memory fallback |
| Icons | lucide-react |
| Data Source | CelesTrak GP API (TLE format) |
| Language | TypeScript 5 |
flowchart TD
CT["CelesTrak GP API\n(TLE format)"] --> F["fetcher.ts\nparseTLEText · fetchCategory"]
F --> R["redis.ts\nUpstash Redis · in-memory fallback"]
R --> A1["/api/satellites"]
R --> A2["/api/groups"]
R --> A3["/api/search"]
R --> A4["/api/satellite-media"]
A1 --> Z["satmapStore.ts\nZustand · loadCategory"]
A2 --> Z
A3 --> Z
A4 --> Z
Z --> P["propagation.ts\nSGP4 · ECI→scene · trail computation"]
P --> G["GlobeScene.tsx\nR3F Canvas"]
G --> E["Earth.tsx"]
G --> S["SatellitePoints.tsx"]
G --> OT["OrbitTrail.tsx"]
G --> SF["Starfield.tsx"]
G --> CC["CameraController.tsx"]
G --> HUD["HUD Layer\nHeader · StatsBar · CategorySidebar\nSatelliteInfoPanel · SearchBar · Toolbox"]
Six abstraction layers (detailed in /docs):
- UI/HUD Layer — All overlay components (
/components/hud) - State Layer — Zustand store (
satmapStore.ts) - Graphics Layer — R3F scene, Earth shaders, orbit trails (
/components/globe) - Controller Layer — SGP4 propagation, trail math, coordinate transforms (
propagation.ts) - Data Layer — CelesTrak fetching, TLE parsing, Redis caching (
fetcher.ts,redis.ts) - Types Layer — Shared interfaces and category metadata (
satellite.ts)
Prerequisites: Node.js 18+
npm install
npm run devOpen http://localhost:3000 — the root redirects to /tracker.
Without Redis env vars the app runs fine using an in-memory fallback cache. You'll see a console warning; this is expected.
Build and preview:
npm run build
npm run startCreate a .env.local in the project root:
# Upstash Redis (optional — falls back to in-memory if omitted)
UPSTASH_REDIS_REST_URL=your_upstash_redis_url
UPSTASH_REDIS_REST_TOKEN=your_upstash_redis_token
# Cache TTL in seconds (default: 1800 = 30 min)
CACHE_TTL_SECONDS=1800
# Media cache TTL in seconds (default: 604800 = 1 week)
CACHE_MEDIA_TTL_SECONDS=604800
# Hard cap on satellites returned per category (default: 500)
MAX_SATS_PER_GROUP=500All variables are server-only; none are exposed to the client bundle.
| Script | Action |
|---|---|
npm run dev |
Start Next.js development server |
npm run build |
Compile production build |
npm run start |
Serve the production build |
npm run lint |
Lint with ESLint + eslint-config-next |
satmap7/
├─ src/
│ ├─ app/
│ │ ├─ (pages)/ # Secondary pages (about, docs, logs)
│ │ │ ├─ about/page.tsx
│ │ │ ├─ docs/page.tsx # In-app technical documentation
│ │ │ ├─ logs/page.tsx
│ │ │ └─ layout.tsx
│ │ ├─ api/
│ │ │ ├─ satellites/route.ts # GET /api/satellites?category=<cat>
│ │ │ ├─ groups/route.ts # GET /api/groups (all category meta)
│ │ │ ├─ search/route.ts # GET /api/search?q=<query>
│ │ │ └─ satellite-media/route.ts
│ │ ├─ tracker/ # Main tracker route
│ │ │ ├─ TrackerClient.tsx # Globe + HUD composition
│ │ │ └─ page.tsx
│ │ ├─ layout.tsx
│ │ └─ page.tsx # Redirects → /tracker
│ ├─ components/
│ │ ├─ globe/ # R3F scene components
│ │ │ ├─ GlobeScene.tsx # Canvas root, loads all categories on mount
│ │ │ ├─ Earth.tsx
│ │ │ ├─ SatellitePoints.tsx
│ │ │ ├─ OrbitTrail.tsx
│ │ │ ├─ Starfield.tsx
│ │ │ ├─ CameraController.tsx
│ │ │ └─ SatelliteHoverBlock.tsx
│ │ └─ hud/ # Overlay UI
│ │ ├─ Header.tsx
│ │ ├─ StatsBar.tsx
│ │ ├─ CategorySidebar.tsx
│ │ ├─ SatelliteInfoPanel.tsx
│ │ ├─ SatelliteMediaPanel.tsx
│ │ ├─ SearchBar.tsx
│ │ ├─ TrailToggle.tsx
│ │ ├─ BottomBar.tsx
│ │ ├─ DeselectionChip.tsx
│ │ ├─ Toolbox.tsx
│ │ ├─ mobile/ # Mobile-specific HUD variants
│ │ └─ pages/ # Reusable page content blocks
│ ├─ lib/
│ │ ├─ satellite/
│ │ │ ├─ fetcher.ts # CelesTrak fetch · TLE parse · normalize
│ │ │ └─ propagation.ts # SGP4 · ECI→scene · trail · orbit classify
│ │ └─ cache/
│ │ └─ redis.ts # Upstash Redis client · in-memory fallback
│ ├─ store/
│ │ └─ satmapStore.ts # Zustand store (categories, propagated, UI state)
│ └─ types/
│ └─ satellite.ts # All shared types, category metadata, API shapes
SATMAP7 uses three stacked caching layers to protect CelesTrak's servers and keep latency low:
| Layer | Mechanism | TTL |
|---|---|---|
| 1 — Next.js fetch | next: { revalidate } on fetch() |
30 min |
| 2 — Upstash Redis | Distributed key-value store | 30 min (configurable) |
| 3 — In-memory dict | Module-level Map fallback |
same TTL |
Search results are cached separately with a 5-minute TTL (short enough to stay fresh, long enough to absorb repeated queries).
Media descriptors (satellite info text) are cached for 1 week.
TLE data is accurate enough for visualization purposes at a 30-minute refresh cycle. It is not suitable for collision prediction or high-precision tracking.
| Category | Color | CelesTrak Group | Default Cap |
|---|---|---|---|
| Starlink | #00c8ff |
starlink |
500 |
| OneWeb | #a78bfa |
oneweb |
300 |
| Space Stations | #39ff14 |
stations |
50 |
| Weather | #fbbf24 |
weather |
200 |
| Navigation/GPS | #fb923c |
gps-ops |
100 |
| Debris | #475569 |
cosmos-2251-debris |
300 |
| Other Active | #94a3b8 |
active |
400 |
Caps are enforced server-side and can be overridden globally with MAX_SATS_PER_GROUP.
Vercel (recommended)
- Import the repo in Vercel. Framework: Next.js. Build command:
npm run build. Output:.next. - Add
UPSTASH_REDIS_REST_URLandUPSTASH_REDIS_REST_TOKENin the Vercel environment variables panel. - The app is fully edge-compatible. Upstash communicates over HTTPS, so it works with Vercel's serverless functions without persistent TCP connections.
Any Node host
npm run build
npm run start # binds to PORT env var, defaults to 3000- Server-client record misalignment (v1.0) — If Redis refreshes a category from CelesTrak and drops a satellite record, that satellite may still appear in Zustand on connected clients. Searching for it returns no result despite it appearing on the globe. A v1.1 patch will move search execution partially client-side to close this gap.
- Propagation failure logging — Failed SGP4 calls currently log
CATASTROPHIC::Alignment Errorto the console. These are non-fatal and have not been observed in production, but the label will be revised in v1.1.
- v1.1 — Client-side search fallback to eliminate record misalignment edge case
- v1.1 — Revise propagation failure logging
- v1.2 — Ground track projection (lat/lon path overlay on a 2D map panel)
- v1.2 — Pass-prediction for a given ground location
- v2.0 — Remove
CelestrakGPElementintermediary type; populateSatelliteRecorddirectly from raw TLE (marked deprecated in v1.0) - v2.0 — Indefinitely-cached satellite descriptors to reduce media latency
