My niece Meicken keeps bees on a farm in Mehrum, a small district of Hohenhameln in Lower Saxony. She wanted to know what the actual bee situation looks like in her specific corner of Germany — not garden-magazine claims, not NGO talking points, but data she could trace back to a primary source. Ten days later that turned into bienenatlas.de: a real-time dashboard for the Wildbienen situation in 121 German municipalities, with four data sources, 235 illustrations, and one rigorous editorial promise — no fabricated numbers, no marketing language, every claim traceable to a cited primary source.
V1: a dashboard for a single farm
The first iteration was deliberately small: one Astro dashboard that pulled Apidae records (taxonKey 4334) for the Mehrum bounding box from the GBIF API, counted the top species, compared the last 30 days against the same window the year before, and rendered the individual records as cards. No database, no state, no build-time data step — everything SSR on each request, with a five-minute edge cache on Netlify. Six days from the first sketch to the live page, sprints MEICKEN-MVP-001 through MVP-012.
Meicken saw it on her phone at the family breakfast and asked what a “bbox” was. That was the moment it became clear: this works for her as a farm tool, but it does not scale as a platform idea. Nobody outside Mehrum would land on ?gemeinde=hohenhameln. It was a farm tool, not an atlas.
The pivot to 121 municipalities
The rebuild started from one question: if every German municipality had its own URL, what would that cost? The answer is the architecture in production today:
/— national landing with aggregated data across every municipality/<bundesland>/— 16 federal-state hubs (/niedersachsen/,/bayern/, …) with regional data and a list of every municipality in that state/<bundesland>/<gemeinde>/— 121 individual municipality dashboards, the same shape as V1 but with local data
The file src/data/regions.ts holds, for every municipality, an ags slug, a name, a land code and a bounding box. Astro 6 supports SSR dynamic routes without getStaticPaths() — the file src/pages/[bundesland]/[gemeinde]/index.astro resolves the slug pair to a region at request time, pulls the GBIF data for the bbox, renders the page, caches for five minutes at the edge. When a user types /bayern/halle/ (Halle is in Saxony-Anhalt, not Bavaria), I 301-canonicalise to /sachsen-anhalt/halle/ instead of 404-ing.
Four data sources, aggregated per municipality
The biggest risk with 134 same-shaped pages is Google’s Helpful Content Update — near-duplicate content gets demoted. The fix is not fewer pages, it is more genuine content per page. Four data sources give each municipality dashboard unique substance:
1. GBIF (Global Biodiversity Information Facility)
Scientific aggregator for occurrence data. Auth-free REST API, filtered by Apidae family + Germany + municipality bbox. Per page: total records, top Apidae species (facet API), three-year annual trend, 30-day window vs. last year, individual-record cards with photo-license filtering (only CC0 / CC BY / CC BY-SA is rendered commercially-safe).
2. DWD Phenology (German Weather Service Climate Data Center)
Long-term flowering-onset means for seven bee-relevant plants (Salweide, Schlehe, Löwenzahn, Apfel, Holunder, Sommerlinde, Heidekraut). The script scripts/build-phaenologie-context.mjs reads the annual_reporters/historical/ CSVs from opendata.dwd.de (cp1252-encoded, semicolon-separated, with an eor end marker), computes the Haversine distance to the nearest station, and writes src/data/phaenologie-context.json. Result: 121 of 121 municipalities mapped, nearest-station median distance 2.0 km, 769 (station × plant) averages. Hohenhameln gets the Soßmar DWD station 0.6 km away with 14–30 years of data per plant.
3. OpenStreetMap Overpass — protected areas
One query per municipality bbox on boundary=protected_area + leisure=nature_reserve + boundary=national_park. Classified by protect_class into NP/NSG/FFH/LSG, deduplicated, capped at five per municipality. 119 of 121 municipalities have at least one protected area from OSM, 559 total (29 NP, 507 NSG, 7 LSG, 16 other). Includes Wikipedia and official website links when OSM has the tags.
4. NABU naturgucker + Wikipedia (per-municipality context)
Wikipedia infobox scrape per municipality: postal code, district, population with year, elevation, area. Verified through de.wikipedia.org directly with attribution. Plus: NABU federal-state association URL per state (zero-trust verified, 14 NABU + Bavaria→LBV + Saarland→nabu-saar.de), nature-protection authority URL (13 ministries direct, BE+SL bot-blocked → WebFetch cross-verification), insect-protection action-plan status (10 present, 6 none known — honestly flagged).
235 images with Nano Banana
Google’s Gemini 2.5 Flash Image (internal name “Nano Banana”) generates 1024×1024 PNGs at about USD 0.039 per image on the standard tier. Five image-generation sprints later, 235 illustrations with a consistent watercolor aesthetic exist:
| Set | Count | Cost |
|---|---|---|
| National hero + OG card | 2 | $0.08 |
| Federal-state heroes (16) | 16 | $0.62 |
| Municipality heroes (121) | 121 | $4.72 |
| DIY heroes (5) + step-by-step (16) | 21 | $0.82 |
| /about + /404 + 3 social variants | 5 | $0.20 |
| Protected-area flagships (16) + Apidae genus moodboards (10) | 26 | $1.02 |
| Per-state 1:1 accents (16) | 16 | $0.62 |
| 5 explanatory scenes + 3 panoramic dividers | 8 | $0.32 |
| 8 logo concepts | 8 | $0.31 |
| Total | 223 | $8.71 |
Plus 12 favicon and PWA icons via sharp (programmatically derived from favicon.svg) — zero cost. Total image-generation budget: less than nine euros against the €260 free-trial credit Google Cloud grants on billing activation.
Three lessons from the Nano-Banana workflow:
- Typography is unreliable. Across the eight logo concepts the model rendered “Bieneatlas” instead of “Bienenatlas” — the second
ndropped. Image models cannot do text reliably. Logos must be finalised vectorially with real typography. - Consistency requires a style block. A reused prompt suffix guarantees consistent look across every generation:
Painted in soft watercolor with a vintage natural-history-book aesthetic, warm honey-amber and forest-green palette. Soft golden-hour light. Hand-illustrated, not photoreal. No text, no labels, no people.Every generation references this block. - Rate limits and free-tier changes are a trap. Gemini 2.5 Flash Image has no free tier; the project needs billing enabled. Once active, the free-trial credit covers everything. My generator script (
scripts/nano-banana-gen.mjs) has 429-retry with backoff that parses theretryDelayfield out of the Gemini error body.
Sedcards with full scientific depth
Every Apidae species gets a species portrait at /art/<gbif-speciesKey>/. This page is the densest in data on the platform — the goal is a scientific reference tool with visual clarity at the same time. The layout is two-column (main + sticky sidebar) with five sidebar cards:
- Related species — same-genus lookup through the GBIF search API
- Live phenology today — compares today’s day-of-year against the long-term flowering-onset average at the local DWD station. Pulses softly when “active right now”,
prefers-reduced-motionsafe - Look-alikes — curated confusion partners sourced from Westrich (2018), nine species hand-verified, zero fabrications
- Where observed — top-5 federal states from the GBIF
stateProvincefacet, linked to the relevant municipality - Scientific sources — eight WebFetch-verified external URLs. The German Federal Agency for Nature Conservation (BfN) has zero bee species in its species-portrait database — honestly omitted instead of fabricated. westrich.de returned ECONNREFUSED on probe — cited as a printed book, not as a URL. wildbiene.com and the Wildbienen-Kataster Stuttgart were verified as universal links.
Plus three new main-column sections per sedcard: full taxonomic classification (<dl>), original description with author + year (only when GBIF provides it — otherwise omitted), habitat and phenology cross-section.
SEO and AEO architecture
A systematic SEO audit of the platform produced 24 findings (2 blockers, 8 high, 9 medium, 5 low) — 14 of them fixed inline:
robots.txtoriginally blocked GPTBot, ChatGPT-User, anthropic-ai, ClaudeBot, PerplexityBot, Google-Extended — every AI crawler was disallowed. After explicit ratification, reversed toUser-agent: * Allow: /. The rationale is documented in the file header: content originates from open data (CC-BY GBIF, ODbL OSM, DWD-open), nothing is IP-protectable, AEO citations in ChatGPT/Claude/Perplexity are the most valuable traffic lever in 2026.llms.txtadded per the llmstxt.org spec — a curated site overview for AI crawlers, including the UWG §5 sourcing discipline statementHowToschema on the four DIY guides (insect hotel, ground-nester habitat, wildflower strip, glyphosate alternative) with step lists, materials, tools, durations — eligible for Google AI Overview snippetsPlaceschema on every municipality page with@idandcontainedInPlacepointing at the federal-state hub — site-internal entity graph- Cross-municipality sibling block on each of the 121 municipality pages links to every sister city in the same state — closes the biggest internal-linking gap
- Sitemap priorities by route type via a
serializecallback: 1.0 landing, 0.9 hubs, 0.8 municipalities, 0.7 DIY, 0.3 legal
In parallel, 62 new internal <a href> links went into eight files. The critical gap was /art/<speciesKey>/ with zero internal links — closed at nine. Every page now carries between 8 and 14 internal links plus a “Beobachtungen in deiner Gegend” CTA box that funnels into the pilot municipality or the federal-state map.
Domain setup
Two curated domains: bienenatlas.de as the canonical primary plus wildbienenkarte.de as an SEO alias with a 301 redirect to the primary host. Mail stays separate from web — its own MX configuration, independent of how the site is served.
The DNS zone cleanup was surgical: flip the apex to the web host, drop AAAA records where unneeded, set www as a CNAME, bump the SOA serial — everything else untouched. Mail records (MX, SRV, DKIM, SPF) stay intact. SSL certificates provisioned within minutes for all four hostnames (apex and www on each domain).
Four host-redirects make every alias host land on the canonical apex — no duplicate-content risk. The path-splat substitution keeps /niedersachsen/hohenhameln/ working on any alias as a 301 to bienenatlas.de/niedersachsen/hohenhameln/.
Google Search Console: sc-domain:bienenatlas.de registered as a domain property via DNS TXT verification, sitemap submitted (146 URLs, 0 errors, 0 warnings).
What’s next
Three sprints remain open:
- Watch the real indexing. Google accepted the sitemap index and queued 146 URLs for crawling. How many will be indexed and when is unpredictable — typically one to four weeks for a fresh domain. Reindex requests for the top 20 municipalities come in phase 2.
- More municipalities. Currently 121 — Germany has roughly 11,000 municipalities. The bbox definitions are hand-curated; a script could automate OSM Nominatim queries to expand the list to 500 or 1,000. Trade-off: every new municipality needs a generated hero image (Nano Banana), Wikipedia enrichment run, OSM protected-area query — roughly $4 per 100 new municipalities plus 30 minutes of pipeline time.
- Logo finalisation. Eight concepts generated; the final wordmark will be set vectorially with Inter or Söhne as a CSS layer on top of the chosen mark. That’s a Figma sprint, not a code sprint.
Stack
Repository (private, Wender Media Brand Property License): arnoldwender/wm-brand-bienenatlas. Live: bienenatlas.de.
What works here is not the technology — every component is standard. What works is the editorial discipline: no claim without a cited source, every URL hand-verified, every estimate flagged as such, every action-plan status honestly split between present and none known. That is what Meicken wanted at the start — and it is also what now helps 121 other municipalities.