| app | ||
| components | ||
| lib | ||
| .env.example | ||
| .gitignore | ||
| next.config.ts | ||
| package-lock.json | ||
| package.json | ||
| postcss.config.mjs | ||
| README.md | ||
| tsconfig.json | ||
Wayfarer
A black-and-gold weather station: pixel-art skies, a heart-based comfort meter, hourly and 14-day forecasts you can switch between, an animated radar map, official alerts and air quality, and a switchable data source. Built with Next.js 16 (App Router), TypeScript, Tailwind CSS v4 and Leaflet.
Run it
npm install
cp .env.example .env.local # add whichever keys you have
npm run dev # http://localhost:3000
With no keys at all, everything except the OpenWeather map layers still works, on Open-Meteo.
What it shows
| Section | What's in it |
|---|---|
| Alerts | Official NWS watches and warnings for US places; provider alerts elsewhere (WeatherAPI only). Each opens to show the full text. |
| Hero | Temperature, conditions, today's high/low, a precipitation outlook for the next 24 h, comfort hearts. |
| Conditions | Feels like, dew point, humidity, wind with direction and gusts, UV index, air quality (US AQI + PM2.5), pressure, visibility, cloud cover, precipitation, sun times, moon phase. |
| Hourly | 24 h / 48 h / 7-day chart (temperature, feels like, chance of precipitation) with a crosshair readout of any hour, or a full table. |
| Daily | Up to 14 days with a range bar per day. Tap a day for precipitation, wind, UV, humidity, sunrise, sunset and daylight. |
| Radar and maps | Animated radar (worldwide or high-detail US), plus OpenWeather precipitation, cloud, temperature, wind and pressure maps. |
A reading a provider doesn't offer is simply hidden. Nothing shows a fake zero.
Providers and what each delivers
| Provider | Env vars | Hourly | Daily | Notes |
|---|---|---|---|---|
| Open-Meteo | none | 7 days, 1 h steps | 14 days | Free, no key. The deepest data here. |
| OpenWeather | OPENWEATHER_API_KEY, OPENWEATHER_PLAN |
5 days, 3 h steps | 5 days | free plan. No dew point or UV. |
| 48 h, 1 h steps | 8+ days | onecall plan (One Call 4.0). Separate subscription, 1,000 calls/day free. |
||
| WeatherAPI | WEATHERAPI_API_KEY, WEATHERAPI_PLAN |
up to 3 days | 3 days (14 paid) | Includes its own alerts. |
| Tomorrow.io | TOMORROW_API_KEY |
5 days | 6 days | Free plan: 25 calls/hour. Responses are cached for 10 minutes. |
Every provider also gets these, from free sources:
- Air quality and time zone: Open-Meteo's air-quality API (CAMS data), so the AQI scale is the same whichever provider you pick.
- US alerts: the National Weather Service. Set
NWS_CONTACTto an email or URL, because NWS asks apps to identify themselves. - Moon phase: calculated locally.
Radar sources
| Layer | Source | Coverage | Frames | Limits |
|---|---|---|---|---|
| Radar | RainViewer | Worldwide | Last hour, 10-min steps | Max zoom 7, past frames only, personal/educational use only (since Jan 2026) |
| US radar | NWS NEXRAD via Iowa State IEM | US only | Last 50 min, 5-min steps | Shared academic service: fine for personal use, not for thousands of users |
| OpenWeather × 5 | OpenWeather map tiles | Worldwide | Current only | Needs OPENWEATHER_API_KEY; tiles are proxied by /api/tiles so the key stays secret |
US places open on the US radar layer automatically. The basemap is CARTO's dark style;
set CARTO_API_KEY (free at carto.com/basemaps/apikey) so /api/tiles can strip CARTO's
watermark while keeping the key server-side.
How it fits together
Browser Server (Next.js route handlers) Outside
─────── ─────────────────────────────── ───────
WeatherApp ── /api/providers ──▶ which keys exist + each plan's limits
── /api/geocode ──▶ lib/weather/geocode.ts ─────────────────────▶ OpenWeather / Open-Meteo
── /api/weather ──▶ in parallel:
providers/<name> (required) ──────────▶ chosen provider
extras/air-quality (optional) ──────────▶ Open-Meteo AQ
extras/nws-alerts (optional) ──────────▶ api.weather.gov
RadarPanel ── /api/radar ──▶ RainViewer frame index ─────────────────────▶ RainViewer
── /api/tiles/… ──▶ OpenWeather + CARTO tile proxy ─────────────▶ OpenWeather / CARTO CDN
── NEXRAD + RainViewer tiles load straight from their servers (no keys involved)
If an optional source fails, its section just disappears; only a failed forecast shows an error.
lib/weather/types.ts: the one normalized shape. Units are metric, times are UTC, and missing readings arenull.lib/weather/providers/: one adapter per provider. OpenWeather has a folder withfree.tsandonecall.ts.lib/weather/extras/: air quality and NWS alerts.lib/radar/layers.ts: every map layer and how to load its frames.lib/display.ts: unit conversion, time-zone-aware formatting, UV/AQI labels, moon phase, precipitation outlook.components/forecast/:ForecastPanel(the switch),HourlyView+HourlyChart,DailyView.components/radar/:RadarPanel(controls) andRadarMap(Leaflet, browser-only).components/ui/ChoiceChips.tsx: the chip group used for providers, views, ranges and map layers.
Adding a provider
- Write
lib/weather/providers/foo.tsexporting aWeatherProvider, withgetForecast()andlimits(). - Add
"foo"toProviderIdintypes.ts. - Register it in
providers/index.ts, then add its key to.env.example.
Styling (Tailwind v4)
There is no tailwind.config.js; v4 is configured in CSS.
app/globals.cssholds the theme tokens in@theme. Colors like--color-goldbecomebg-gold,text-goldand so on, and the fonts becomefont-display,font-pixelandfont-sans. It also holds one custom utility,chamfer(the cut corners), Leaflet control overrides, and a few base rules.- Every other style is a utility class in the component that uses it. Layout is mobile-first, with
sm:(640px and up) for the desktop arrangement. lib/styles.tsholds the class strings used by more than one component.- To resize a frame's corners, add
[--cut:22px]to thatSlot'sclassName. Put fixed heights on theSlotitself, not its inner element, which is alreadyh-full. - The Leaflet overrides sit outside any
@layerand are prefixed with.leaflet-container. Leaflet's own CSS is unlayered and loads later, so anything weaker loses. - The hourly chart is hand-written SVG with no chart library. Temperature and chance of precipitation sit in separate bands on one time axis rather than sharing two y-axes.
Mobile
- Uses
viewport-fit=coverwith safe-area insets, 44px tap targets and 16px inputs (so iOS doesn't zoom on focus). - Chips, the hourly chart and the forecast strip scroll sideways; the page itself never does.
- The map ignores the scroll wheel, so scrolling the page doesn't zoom it; use pinch or the +/− buttons.
- "Use my location" requires HTTPS on phones. To test on your Android over LAN, use a Vercel preview deploy.
- Radar autoplay and the floating icon stop when the OS asks for reduced motion.
Deploy & Legal Compliance
- Vercel: import the repo and add your environment variables (
OPENWEATHER_API_KEY,CARTO_API_KEY,NWS_CONTACT, plus any optional provider keys) in Project Settings. - PM2:
npm run build && pm2 start npm --name wayfarer -- start. - CARTO domain lock: once deployed, restrict
CARTO_API_KEYto your production domain atdashboard.basemaps.carto.com. All/api/*routes also reject cross-siteSec-Fetch-Site: cross-sitehotlinking automatically. - Built-in attributions:
SiteFooter,PlaceSearch,AlertsBanner, andRadarMaprender the required live backlinks and license credits for OpenWeather (CC BY-SA 4.0/ODbL), Open-Meteo (CC BY 4.0), Copernicus Atmosphere Monitoring Service (CAMS), GeoNames (CC BY 4.0), WeatherAPI.com, Tomorrow.io, OpenStreetMap, CARTO, RainViewer, Iowa State IEM, and NOAA/NWS. - Non-commercial vs. commercial publishing:
- Free, ad-free personal / portfolio site: compliant across every source out of the box.
- Commercial site (ads, paid tiers, or business use): OpenWeather, WeatherAPI.com, NWS, and CARTO (
CARTO_API_KEYincludes 1M commercial tiles/month) permit commercial use within plan quotas, whereas Open-Meteo (forecast, geocoding, air quality), RainViewer, and Tomorrow.io free tiers are non-commercial only and require paid commercial plans or disabling before monetizing.