← Back to blog

18 April 2026 · 612 words

386 landing pages: giving every destination a page of its own

producttechnical
Three releases in one arc. The home page does one job well — sunny escape RIGHT NOW from YOUR origin — but it only surfaces 10-15 destinations per session. We have 386. Time to give each one a URL.

The hidden 371

The home page ranks, filters, and picks. By design, most of the catalog never renders on a given visit. A Basel user on a Tuesday sees maybe 12 destinations before the joystick runs out of band. The other 374 sit in memory, invisible.

That's fine for the product mission — what's sunny right now from where I am — but terrible for discoverability. Someone Googling "sirmione garda day trip" or "st anton sun" or "milan train basel" had nowhere to land except a generic home page that would re-rank around their origin, not the query they actually typed. No canonical URL per destination meant nothing for Google to index against the long tail.

Fixing that took three releases across one day.

1.10.0 — the index

/destinations is a new top-level route: a server-rendered grid of all 386, statically prerendered, filterable in the client.

The filter row stacks a country chip strip (🇨🇭 🇦🇹 🇩🇪 🇮🇹 🇫🇷 🇱🇮, active one amber-ringed), a type multi-select with emoji affordances, a free-text search across name + region, and a sort dropdown (A–Z / lowest altitude / highest altitude). Cards are responsive 2/3/4/5 columns by breakpoint, each with the vintage stamp via next/image for lazy loading.

The server component in src/app/destinations/page.tsx only ships five fields to the client (id, name, region, country, altitude_m, types) to keep the payload tight — full metadata stays server-side for SEO. Tapping a card deep-linked to /?dest=<id>, reusing the 1.9.4 plumbing to auto-flip the matching list card on the home page.

1.10.1 — footer declutter + a stamp pass

Once All destinations and Map joined the footer, the row wrapped awkwardly on mobile next to the version string and API links. So they moved: footer slimmed to Blog / About / Legal, and /about grew an Explore card with three tiles — Home, Map, All destinations — stacked on mobile, side-by-side on desktop. Anyone who lands on /about to figure out what FOMO Sun is now has the three primary surfaces one tap away.

Aside: the same release shipped scripts/optimize-stamps.py — a palette quantize pass (MEDIANCUT + Floyd-Steinberg) that dropped the six PIL-overlaid stamps from 1.9.6 from ~200KB to ~80KB each. Idempotent. 3% saved globally, 60% on the overlaid ones.

1.11.0 — one static page per destination

The real unlock. src/app/destinations/[id]/page.tsx with generateStaticParams() returning all 386 IDs. Next.js pre-renders every page at build time — the build output shows ● SSG for all 386, each 186 B route, 101 kB first-load JS (mostly shared chunks).

Each page carries: breadcrumbs (Home › All destinations › <name>), the vintage stamp priority-loaded as hero, country flag + region + altitude + types, description, three-step trip plan, a primary See today's sun → CTA that deep-links to /?dest=<id> to hand off to the live hero flow, secondary links to Google Maps Navigate / SBB timetable / the curated tourism-board URL from 1.9.3, a Nearby carousel of the five closest destinations by haversine, and a JSON-LD TouristAttraction schema with geo coords.

Why this shape

  • Long-tail unlock. "Sirmione Garda day trip" / "St. Anton sun" / "Vaduz from Zurich" now have real landing pages with canonical metadata and structured data — not a generic home page that re-ranks around the visitor's GPS.
  • Crawler depth. Each page links to at least seven others: five nearby + breadcrumbs + back to index. The graph is dense enough that Google can walk from any entry point to the rest of the catalog.
  • Build cost is modest. 386 static HTML files at ~101 kB first-load each, total extra uncompressed HTML around 3 MB. Vercel eats it.

The design tradeoff

One honest note. The 1.10.0 index cards originally deep-linked directly to /?dest=<id> — one tap from "I'm browsing" to "I'm looking at live sun data." Felt fast. Once 1.11.0 landed, I retargeted those card links to the detail page instead. The direct deep link was objectively fewer taps, but it also collapsed two different modes — catalog browse and live forecast — into one surface that had to do both jobs. The new flow lets the catalog breathe: browse cards → detail page → then commit to the live sun forecast via the CTA. A bit slower. Probably right.

Next

386 new pages for Google to crawl. Next up: custom OG images per destination so a shared link renders a real preview card, not a generic FOMO Sun logo.

Shipped 2026-04-18 as v1.10.0 + v1.10.1 + v1.11.0. Full release notes in docs/RELEASES.md.