I run a surf forecast for 20 breaks in Morocco on EUR 0/month. Here's the stack.
I live on the Taghazout coast in Morocco - a strip of Atlantic between Agadir and Imsouane that's basically one long right-hand point break after another. Two years ago the only way to know if tomorrow was worth it was to check three different global forecast sites, none of which knew the difference between Anchor Point and the beach break 400m south of it. So I built taghazout.io. It now covers 20 named breaks, runs in 10 languages, and costs me nothing per month. Here's how it's actually put together - including the parts I'd do differently. The stack is deliberately boring Hand-rolled PHP. No framework, no build step, no node_modules. About 4,800 files, server-rendered, no hydration. That sounds like a confession, but it was the right call for one reason: my readers are on phones, on cafe Wi-Fi, often on 3G. A server-rendered page that ships HTML and a little CSS beats anything I could have built with a client-side framework in that environment. Time-to-content is the only metric that matters when someone is standing on the beach deciding whether to paddle out. The hosting is a cheap shared plan. The forecast data is free and open. The whole thing runs at EUR 0/month recurring, which was a hard constraint from day one. The interesting part: two ocean models that disagree The forecast blends two sources: - Open-Meteo (CC BY 4.0) - the primary, with a marine endpoint that covers our coastal cells. - NOAA WaveWatch III via PacIOOS - the second opinion. Here's the thing nobody tells you: they disagree, a lot. On the same hour at the same break I've seen WaveWatch read ~55% higher than Open-Meteo (1.36m vs 0.88m). Offshore models resolve coastal bathymetry badly, and our points are exactly the kind of close-in, shallow-reef setups where that bias shows up. The wrong fix is to pick one and pretend. What I did instead: - Run both, cache both. - Compute agreement over a 72-hour window - a Pearson correlation on the swell rhythm plus a circular difference on direction (you can't average 350 and 10 degrees naively). - Show the user a confidence flag: agree / mixed / diverge. A recent live read for Anchor Point: r=0.88, 8 degrees direction difference over 73 hours, so "agree". When it says diverge, that's genuinely useful information - it means treat the numbers with suspicion, not that the site is broken. Some gotchas from the WaveWatch integration, since these cost me a full evening: - The ERDDAP query brackets must be percent-encoded as %5B /%5D . - The time stop must be the literal (last) , not a future timestamp you computed. - The coastal grid cell you'd expect (lon 350.5) is dry land. You have to fall back half a degree offshore to get swell at all. Honest data beats impressive data The tide readout says, in the payload itself: "modelled sea level, relative to this window's low" Because that's what it is. It isn't a harbour gauge, and pretending otherwise would get someone hurt on a reef. Every derived number on the site carries its caveat, and the public feed carries the same note in a source_note field. I'd rather be trusted than look authoritative. I opened up the feed The same data the site's own forecast pages run on is public, keyless JSON: curl "https://taghazout.io/weather-data/_feed.php?loc=anchor-point" { "ok": true, "spot": { "name": "Anchor Point", "area": "North Taghazout" }, "swell_m": 0.66, "period_s": 8.4, "wind_kmh": 11.2, "water_temp_c": 21.6, "surf_potential_percent": 82, "tide": { "label": "Rising tide", "trend": "rising", "height_m": 2.53 } } There's a small MIT-licensed client for it in Python and JS: github.com/MyTaghazout/taghazout-surf, a full API reference, and the spot registry is published as a dataset on Hugging Face. If you build something with it, carry the attribution: weather by Open-Meteo (CC BY 4.0), wave model NOAA WaveWatch III via PacIOOS. What I'd do differently - Ten languages was too many, too early. Nine of them started as machine output and I have spent months undoing chimera sentences - English word order with individual words swapped. If I started again: two languages done properly, and add the rest only when each one has real readers. - I measured nothing for six months. The analytics tag was gated behind cookie consent in a way that made most visitors invisible, so I was optimising blind. Fix your measurement before you optimise anything. Happy to answer anything about the model-blending or the caching layer in the comments. Weather data by Open-Meteo.com (CC BY 4.0). Wave model NOAA WaveWatch III via PacIOOS. Top comments (0)
Comments
No comments yet. Start the discussion.