Build Sheet · July 2026

How SunnySky is built

A dark-mode, social-first weather app that reads sevenindependent forecasts and shows you where they disagree. One TypeScript codebase ships it to iPhone, Android and the web. Here's what I used, why I picked it, and what it costs me to run.

TypeScript
26,492
Forecast sources
6
Data providers
20
Edge functions
12
Cost today
$8/mo

01The shape of it

It's one repo with four packages inside. Two of them are the actual apps — the phone app and the website. The other two are the stuff both apps have to agree on: all the weather logic, and the design tokens.

That split is basically the whole architecture, and it's the decision I'd defend hardest. The maths that draws the consensus line on the chart is literally the same function that fills in the iPhone widget, because they both import it from the same file. There's no second copy to drift out of sync. When I fixed a rounding bug in it once, the app, the website and the widget all got the fix at the same time without me touching three files.

Everything below hangs off that idea: keep the thinking in one place, keep the platforms dumb, and don't pay for anything the government already gives away.

02What I'm actually using

The phone app

I'm a solo dev. If I had to maintain a Swift app and a Kotlin app and a web app separately, I'd ship nothing. So the whole client is one codebase.

ExpoSDK 57
The thing that makes one codebase into three apps. It also handles the annoying parts I don't want to own — push notifications, location permissions, image caching, over-the-air updates. Expo used to mean “you'll hit a wall eventually” and that's just not true any more; I ship a real SwiftUI widget from it.
Real native views underneath, not a website in a wrapper. It matters here because the app is meant to feel like an instrument — scrubbing the chart has to track your thumb exactly, and a webview would feel like mush.
Routing based on the file layout, like Next.js but for the phone. The bit I actually love is Stack.Protected— the whole onboarding → login → app gate is four lines of “this screen only exists if you're signed in”, instead of redirect logic smeared across every screen.
I draw the forecast chart by hand with this. I looked at chart libraries first and none of them do what I needed: a median line whose colour changes along its length with the temperature, the other sources ghosted behind it, and a scrubber that snaps to 30-minute steps. Bending a library into that shape is more work than just drawing it.
Lets me add a genuine SwiftUI widget extension to an Expo project without ejecting. This is the one that surprised me — home screen widgets used to be a reason to go bare React Native, and now they're a config file.

The website

sunnyweather.co is the marketing site, and it also serves the entire app as a web build at /app — so you can try the real thing without installing anything.

Mostly here for one feature: when you share a location from the app, Next generates a live weather image for that exact spot on the fly. That share card is the only thing people who don't have the app ever see, so it's worth a framework.
Pulls in the same design tokens the phone app uses, so the site and the app are genuinely one visual system instead of two things that sort of look alike. This page is styled from those tokens too.
Hosting. One command to deploy, and it just works. Heads up if you're copying this: their free Hobby plan is licensed for non-commercial use only, so the moment you charge for anything you owe them $20/mo.

The backend

This is the part I'd have expected to be hardest and it turned out to be the easiest. It's all one product.

SupabasePostgres · 18 tables
Real Postgres, not a document store, and that's deliberate. The social side is a proper relational mess — following someone is a requestthat gets accepted or declined, answers point back at questions, blocking someone has to filter them out of two different feeds. That's a join problem, and Firebase would have had me writing it by hand.
The rule about who can see what lives in the database, not in my app code. So if I write a buggy screen, it still can't show you someone else's private report — the query just doesn't return the row. This is the single best thing about building on Postgres and it's why I'd struggle to go back.
Edge FunctionsDeno · 12 of them
Little Deno functions that run on Supabase. Every paid weather API key lives in one of these instead of in the app, because a key you ship inside an app is a public key — anyone can pull it out of the bundle. They also cache the weather per area, so a hundred people in one city cost me one API call instead of a hundred.
pg_cron + pg_net5 scheduled jobs
The database schedules its own work. Severe weather sweep every 5 minutes, morning brief every hour, sky events every 3 hours, forecast accuracy logging every night. There is no serversitting somewhere running a queue — it's a cron entry in Postgres that pokes a function.
Supabase Authemail OTP
Passwordless — you get a code by email. No passwords to store, leak, or reset, and one fewer screen to build.

Where the weather comes from

This is the actual product. Most weather apps resell one forecast. I pull seven, plus a pile of government feeds, and show you where they disagree.

Seven forecaststhe consensus
Open-Meteo, MET Norway, the US National Weather Service, Apple WeatherKit, Pirate Weather, OpenWeather and Google Weather. When they disagree the app says so out loud instead of quietly averaging them into a number nobody should trust.
Ten modelsrain vote
Underneath the forecasts are the actual weather models — ECMWF, GFS, ICON, HRRR, UK Met Office, Canada's GEM, Météo-France, Japan's JMA, China's CMA and ECMWF's AIFS, an AI model. I ask all ten whether it rains and count the votes. It costs nothing extra— they arrive on a request I'm already making for the chart.
Government feedsfree, no key
This is the unfair advantage. Storm Prediction Center outlooks, hurricane tracks, NOAA tides, USGS river gauges, airport observations, official climate normals, space weather for the aurora, NASA satellite imagery, RainViewer radar tiles, and lightning from the Geostationary Lightning Mapper aboard GOES-19 — total lightning, Americas only, decoded out of raw satellite granules every five minutes. All public, all keyless, all $0.

Shipping and keeping it alive

Builds the iOS app in the cloud and pushes it to TestFlight in one command. I don't own a build machine and never want to.
Sentry7.11
Tells me when the app crashes on someone else's phone, with the line number. Native only — the web build deliberately reports nothing.
Runs typecheck and builds across all four packages and skips anything that hasn't changed. Keeps a monorepo from feeling slow.
TypeScript5.8 · strict
Strict mode everywhere. The shared packages export raw source, so there's no build step between me changing a file and both apps seeing it — and the compiler catches the mismatch immediately if I break one of them.
RevenueCatgoing in next
Handles subscriptions so I don't have to implement Apple's receipt validation myself. Free until $2,500/mo in revenue, which is a very fair place to put the paywall. Receipts get checked server-side — the app never gets to declare itself Pro.

03What it costs me

Two columns — what I'm actually paying right now before launch, and what it turns into at roughly 2,000 installs with 50 people subscribed.

ServiceWhat I needNowAt 2k installs
Apple Developer$99/yr, non-negotiable$8.25$8.25
Open-MeteoAPI Standard — commercial licence$0$29.00
SupabasePro — backups, and it stops auto-pausing$0$25.00
VercelPro — required once I charge money$0$20.00
Pirate WeatherPaid once I outgrow the free calls$0$5.00
Apple WeatherKit500k calls included with the $99$0$0
OpenWeatherFree tier is plenty$0$0
Google WeatherMaps Platform free calls for now$0$0
Google Pollen5,000 calls free, then $10/1,000 — cached 6 h per area$0$0
Google Routes10,000 calls free, then $5/1,000 — Pro only, 60/day each$0$0
NOAA / NASA / USGSPublic data, no key, no bill$0$0
SentryFree — 5k errors/mo$0$0
EAS BuildFree — 15 iOS builds/mo$0$0
RevenueCatFree under $2,500/mo revenue$0$0
What I payper month$8.25$87.25

And against that, 50 people paying $2.99:

Subscriptions — 50 × $2.99$149.50
Apple's cut — 15% on the Small Business Program−$22.43
Everything above−$87.25
Left over+$39.82

Break-even is about 33 subscribers. The only reason that number is achievable is that the expensive-sounding parts — seven forecasts reconciled, ten models polled, months of accuracy history — mostly run on free government data and on maths that happens on your phone.

The two Google rows are the ones I watch, because they're the only things here billed per request rather than per month. Both sit at zero by construction rather than by luck. Pollen is a daily index, so it's cached six hours per area— a whole city asking at once is one call, and 5,000 calls a month is a lot of cities. Routes is heavier, so it's Pro-only, capped at 60 trips per person per day, refuses the public key outright, caches by rounded coordinates, and asks for the traffic-unaware tier — which is both the cheapest and the honest one, since live traffic at the moment you ask describes none of the departures being compared. If either line ever stops reading $0, it'll be because the app got popular, and I'd rather find that out from this table than from a bill.

04Stuff I learned the annoying way

Two “free” tiers you can't use commercially

Open-Meteo's free API is non-commercial, and their terms specifically name “apps that have subscriptions” as commercial. Vercel's Hobby plan says the same thing. Both are free today, and both quietly become licence breaches the day I charge anyone — regardless of how little traffic I use.

Nothing warns you. It's $49/mo between them and it has to be paid before the subscription goes live, not after.

One of my Pro features might sit on a $99 plan

Forecast drift — showing how a day's forecast has been revised across recent model runs — needs Open-Meteo's Previous Model Runs API, and that looks like it requires their Professional plan at $99/mo instead of Standard at $29.

That $70 flips +$40/mo into −$30/mo. I need to confirm it with them directly before buying, and if it's true then drift either earns its own $70 or it gets cut.

Where the money isn't going

No servers. No Kubernetes, no Redis, no message queue, no CDN bill, no analytics vendor, no email service, no Mac in a cupboard running builds. The database schedules its own jobs and Apple handles the billing.

05Four things I deliberately didn't use

Every one of these is a thing you'd normally reach for, and skipping them was the right call each time.

No charting library

I hand-draw the forecast chart in SVG. I needed a median line coloured by temperature along its length, every source ghosted behind it, and a scrubber snapping to 30-minute steps — and once you've fought a chart library for a day you realise drawing it yourself is the shorter path.

No map library

The radar map is about 400 lines of hand-rolled tile maths. It draws the basemap, the radar frames and the warning polygons, animates them, and behaves the same on phone and browser. Mapbox and friends bill per map load, which is exactly the cost curve I built the rest of this app to avoid.

No state management library

Just React context and some hooks. The app's state is honestly simple — a location, a forecast, a session. Redux would be ceremony for the sake of it.

No component kit

Design tokens instead. The app has a very specific look — instrument panel, monospace numbers, true-grey surfaces with no blue in them — and starting from someone else's components means fighting their defaults the whole way.