← Projects

Shredders

Real-time powder conditions for 53 ski resorts across 11 regions.

53 resorts · 11 regions · web + iOS · seven data sources

Shredders web app
Shredders iOS app

The Problem

Planning a ski day means checking five different websites. SNOTEL for snowpack. NOAA for the forecast. WSDOT for road conditions. The resort's webcam page. Your group chat for who's actually going. By the time you've pieced it together, the alarm is set for 5 AM and you've talked yourself out of it.

Shredders pulls all of that into one place, on web and iOS: a powder score, 7-day forecasts, weather alerts, an AI chat for trip planning, and the social bits for sorting out who's driving.


Architecture

Data Sources SNOTEL (USDA)
NOAA / Weather.gov
Open-Meteo
NWAC (avalanche)
WSDOT
RainViewer
Resort websites
Ingestion Cheerio (HTML)
Puppeteer (JS-rendered)
REST APIs
Cron schedules
Storage PostgreSQL
24 tables · 14 migrations
Upstash Redis cache
API Layer 87 Next.js routes
Claude chat (streaming)
GPT-4 predictions
Clients Web (React 19)
iOS (SwiftUI)
Push notifications

The backend is a Next.js app. Routes are grouped by what they serve: mountains (conditions, forecasts, powder score, alerts, webcams, lifts, parking, trip advice), auth (email and Apple Sign-In), events (RSVP, carpool, recurring series), social (check-ins, likes, photos), and AI chat. Three Vercel cron jobs send daily event reminders, weather alerts, and powder notifications.

Storage is Supabase Postgres. The schema covers users, events and everything attached to them (attendees, comments, photos, activity feeds, date polls, invite tokens), social features, push tokens, alert subscriptions, sessions, audit logs, mountain status, and scraper health.

The Powder Score

One number from 0 to 100 that answers a single question: is it worth the drive? It weighs recent snowfall (24h, 48h, 7d), temperature at elevation, wind, visibility, current conditions, snow still in the forecast, base depth, and seasonal trends.

Performance

The first version of the frontend fired off dozens of API calls on page load. Batch endpoints brought that down to three. A 10-minute response cache keeps Supabase query volume in check, and the iOS app launches with two calls. Images are AVIF/WebP and lazy-loaded.


Decisions

Why Supabase over Firebase?

I wanted real-time subscriptions, auth, and a relational database in one place, and I didn't want to run infrastructure for a side project. Supabase gave me Postgres with real SQL, built-in auth that handles Apple Sign-In, and auto-generated APIs. The auto-generated APIs are quick to build against, but you give up fine control over query tuning as the number of endpoints grows.

Why scrape resort websites?

No resort offers a usable API. Lift status, webcam URLs, parking capacity all live on PHP pages that haven't been touched since 2014. Cheerio parses the static HTML, Puppeteer handles the JavaScript-rendered pages. It's fragile: every time Crystal Mountain redesigns their snow report, a scraper breaks. So I track scraper health in its own tables and check it on a cron.

Why dual platform from day one?

People check conditions at 5 AM from bed, deciding whether to make the drive. That's a phone moment. The web app is better for planning, with the bigger screen, map view, and event coordination, but the iOS app is where most of the use happens. SwiftUI was quick to build, and powder-alert push notifications only make sense on the phone.

Why so many small API routes?

Each route does one thing, so each one is easy to reason about. Lots of small endpoints over a few big ones let me move fast: add a feature, add a route. The cost is surface area. A refactor touches a lot of files, and the frontend got chatty, which is what the batch endpoints fixed.


Tradeoffs

Scraping is fragile. Resort sites change without warning. Monitoring catches the failures quickly, but a broken scraper still means missing data until I fix the parser. There's no way around that without official APIs.

Free tiers shape the architecture. Supabase and Vercel free tiers are generous but not infinite. Upstash Redis keeps Supabase query volume down, and batch endpoints keep Vercel invocations reasonable. At ten times the usage I'd need paid tiers or a different host.

Dual auth is a lot for a side project. Supporting both email/password and Apple Sign-In means JWT rotation, token blacklisting, session tracking, and rate limiting across two flows. An iOS app needs it, but it's more auth code than a side project usually warrants.


Next.js 16 React 19 Tailwind Supabase Upstash Redis SwiftUI Claude GPT-4 Vercel Cheerio Puppeteer