kew 393cc5f901 fix: call OpenSky API directly from browser to bypass Vercel IP blocks (#1)
* fix: switch flights API to edge runtime, lower timeouts

- Switch to Edge Runtime (30s timeout on ALL Vercel plans, near-zero cold starts)
- Lower FETCH_TIMEOUT_MS from 20s to 8s (prevents Vercel killing the function)
- Lower TOKEN_TIMEOUT_MS from 5s to 3s
- Replace Buffer.from() with btoa() for edge compatibility
- Remove maxDuration (not needed for edge functions)

Fixes 502 Bad Gateway on Vercel deployments caused by Hobby plan's
10s function timeout being exceeded by the 20s fetch timeout.

* fix: revert edge runtime, keep reduced timeouts

Edge runtime caused 500 on Vercel. Reverted to Node.js serverless
with reduced timeouts only:
- FETCH_TIMEOUT_MS: 20s -> 8s
- TOKEN_TIMEOUT_MS: 5s -> 3s
- Restored Buffer.from() for basic auth

* fix: handle DOMException unavailability in Vercel runtime

- Fix TypeError: 'Right-hand side of instanceof is not an object'
  caused by DOMException not existing in Vercel's server runtime
- Use safe abort detection: check err.name === 'AbortError' on Error
  first, then conditionally check DOMException if available
- Restore TOKEN_TIMEOUT_MS to 5s (3s was too aggressive from Vercel)
- Keep FETCH_TIMEOUT_MS at 8s (down from original 20s)

* fix: deploy to Frankfurt, increase timeouts, add retry logic

- Set preferredRegion to 'fra1' (Frankfurt) — closest to OpenSky EU servers
- Increase FETCH_TIMEOUT_MS to 15s and TOKEN_TIMEOUT_MS to 8s
  (OpenSky is slow from cloud IPs, needs more time)
- Add retry logic: 1 retry with 500ms delay before giving up
- Keep safe DOMException handling for Vercel runtime compat

* feat: add Railway proxy for OpenSky API, proxy mode for Vercel route

OpenSky blocks/throttles requests from Vercel's IP ranges. This adds:

1. **Railway proxy** (`proxy/`) — standalone Node.js server that handles
   OpenSky API calls with auth, caching, and CORS. Zero dependencies.

2. **Proxy mode in Vercel route** — when OPENSKY_PROXY_URL env var is set,
   the Vercel /api/flights route forwards requests to the Railway proxy
   instead of calling OpenSky directly.

Setup:
- Deploy proxy/ to Railway with OPENSKY credentials
- Set OPENSKY_PROXY_URL in Vercel to the Railway URL
- Remove OPENSKY credentials from Vercel (only needed on Railway)

* refactor: call OpenSky directly from browser, remove server-side proxy

OpenSky supports CORS (Access-Control-Allow-Origin: *), so we can
call the API directly from the user's browser. This bypasses
Vercel's cloud-provider IPs that OpenSky blocks.

Changes:
- opensky.ts: fetch from opensky-network.org/api directly (was /api/flights)
- use-flights.ts: fix DOMException abort detection
- Remove src/app/api/flights/route.ts (server-side proxy)
- Remove proxy/ directory (Railway proxy)

The app is now fully static — no server-side API routes needed.

* chore: remove server-side API route (now browser-side)

* chore: remove Railway proxy (no longer needed)

* chore: remove proxy package.json

* chore: remove proxy README

* polish: production-grade cleanup, security hardening, remove redundant comments

- Remove redundant JSDoc blocks in opensky.ts, keep only @see link
- Add bounds-clamping for lat/lng parameters (defense-in-depth)
- Fix memory leak: map.on("movestart") listener now cleaned up on unmount
- Validate GA_ID format before interpolating into script tag (XSS defense)
- Remove duplicate canonical <link> tag (already set via metadata.alternates)
- Sanitize JSON-LD output with \u003c escaping to prevent </script> injection
- Use useRef instead of useState for mutable TrailStore class instance
- Fix unused useState import in use-trail-history
- Add Map.displayName for React DevTools
- Fix Tailwind lint: px-[2px] → px-0.5
- Remove unused OPENSKY credentials from .env.example

* chore: push remaining polished files (flight-tracker, use-flights, map)

- flight-tracker: movestart listener cleanup on unmount
- use-flights: clean up redundant comments, fix abort detection
- map: add displayName, remove redundant comment prefix

* chore: polish control-panel (clean comments, Tailwind lint fix)
2026-02-14 15:05:00 +05:30

Aeris

Real-time 3D flight tracking — altitude-aware, visually stunning.

Aeris renders live air traffic over the world's busiest airspaces on a premium dark-mode map. Flights are separated by altitude in true 3D: low altitudes glow cyan, high altitudes shift to gold. Select a city, and the camera glides to that airspace with spring-eased animation.

Live Demo

Stack

Layer Technology
Framework Next.js 16 (App Router, Turbopack)
Language TypeScript
Styling Tailwind CSS v4
Map MapLibre GL JS
WebGL Deck.gl 9 (IconLayer, PathLayer, MapboxOverlay)
Animation Motion (Framer Motion)
Data OpenSky Network API
Hosting Vercel

Getting Started

pnpm install
cp .env.example .env.local
# Optionally add OpenSky credentials — see .env.example
pnpm dev

Open http://localhost:3000.

Architecture

src/
├── app/
│   ├── globals.css            Tailwind config, theme vars
│   ├── layout.tsx             Root layout (Inter font)
│   ├── page.tsx               Entry — renders <FlightTracker />
│   └── api/flights/route.ts   OpenSky proxy with rate limiting + auth
├── components/
│   ├── flight-tracker.tsx     Orchestrator — state, camera, layers, UI
│   ├── map/
│   │   ├── map.tsx            MapLibre GL wrapper with React context
│   │   └── flight-layers.tsx  Deck.gl overlay — icons, trails, shadows, animation
│   └── ui/
│       ├── altitude-legend.tsx
│       ├── control-panel.tsx  Tabbed dialog — search, map style, settings
│       ├── flight-card.tsx    Hover card with flight details
│       ├── scroll-area.tsx    Custom scrollbar
│       ├── slider.tsx         Orbit speed slider (Radix)
│       └── status-bar.tsx     Live status indicator
├── hooks/
│   ├── use-flights.ts         Adaptive polling hook with credit-aware throttling
│   ├── use-settings.tsx       Settings context with localStorage persistence
│   └── use-trail-history.ts   Trail accumulation + Catmull-Rom smoothing
└── lib/
    ├── cities.ts              Curated aviation hub presets
    ├── flight-utils.ts        Altitude→color, unit conversions
    ├── map-styles.ts          Map style definitions
    ├── opensky.ts             OpenSky API client + types
    └── utils.ts               cn() utility

Design

  • Dark-first: CARTO Dark Matter base map, theme-aware UI
  • 3D depth: 55° pitch, altitude-based z-displacement via Deck.gl
  • Smooth animation: Catmull-Rom spline trails, per-frame interpolation between polls
  • Glassmorphism: backdrop-blur-2xl, bg-black/60, border-white/[0.08]
  • Spring physics: All UI transitions use spring easing
  • Responsive: Desktop sidebar dialog, mobile bottom-sheet with thumb-zone tab bar
  • API efficiency: Adaptive polling (30 s → 5 min) based on remaining credits, Page Visibility pause, grid-snapped cache
  • Persistence: Settings + map style in localStorage, ?city=IATA URL deep links

Environment Variables

Variable Required Description
OPENSKY_CLIENT_ID No OAuth2 client ID (recommended)
OPENSKY_CLIENT_SECRET No OAuth2 client secret
OPENSKY_USERNAME No Basic auth username (legacy)
OPENSKY_PASSWORD No Basic auth password (legacy)
NEXT_PUBLIC_GA_ID No Google Analytics measurement ID

Without credentials, anonymous access is used (~10 requests/minute).

License

AGPL-3.0

Description
No description provided
Readme 4.5 MiB
Languages
TypeScript 99.5%
CSS 0.5%