Repository of lobbyranking.de – pull requests are welcome.
This site is generously supported by BrowserStack, www.browserstack.com for cross-browser compatibility.
Built with Next.js and Data Love.
- Next.js 16 with the App Router, exported as a fully static site
(
output: "export"innext.config.ts→npm run buildwritesout/). There is no server runtime in production. - React 19 server components for everything that renders at build time;
a handful of
"use client"components handle the interactive bits (year toggle, category switcher, the embeddable explorer). - TypeScript, strict.
- CSS Modules (
*.module.cssnext to each component) plus a small global stylesheet (app/globals.css). No CSS framework. - d3-scale for the bar-chart scales, gray-matter + marked to turn the per-state Markdown into HTML. That's the whole dependency list.
- Node 22 (
.node-version),>=20.9.0enforced inpackage.json. - Deployed on Render as a static site (build → publish
out/).
Note on the Next.js version: this is a newer Next.js than most references describe.
AGENTS.mdis auto-generated bynext dev; checknode_modules/next/dist/docs/before relying on older App Router conventions.
app/
(main)/ route group for the public site (shares Header/Footer/About)
page.tsx home page: Germany map + ranking explorer + accordion
laender/[slug]/ one page per Bundesland, statically generated
methodik/ methodology page
layout.tsx <html>, metadata, Header/Footer/AboutSection shell
not-found.tsx
(embed)/ route group for the bare iframe embed (no chrome)
embed/ /embed/ – just the ranking explorer, used by external sites
globals.css
robots.ts, sitemap.ts generated at build time
components/ presentational + interactive React components
*.tsx + *.module.css one CSS Module per component
icons/
lib/ all non-visual logic, imported via the "@/lib/..." alias
data.ts loads + normalizes data/data_<year>.json
scoring.ts turns raw indicators into per-category and overall scores
rankingViews.ts builds the ranked bar-chart datasets the UI consumes
states.ts reads content/states/*.md (frontmatter + body)
statePaths.ts SVG path data for the clickable Germany map
constants.ts YEARS, DEFAULT_YEAR, category order, colors
types.ts shared TypeScript types
fonts.ts next/font setup
content/states/*.md editable descriptive text per Bundesland (YAML frontmatter + Markdown)
data/data_<year>.json the ranking data, one file per survey year
scripts/fetch-data.ts refreshes data from the Google Sheet (run manually)
public/ static assets (images, PDFs, fonts) served from /
build.js legacy – leftover from the previous Jekyll/Angular site
Rendering. Every page is generated at build time. app/(main)/laender/[slug]/page.tsx
uses generateStaticParams() over the state slugs, so each Bundesland becomes a
static HTML file. trailingSlash: true keeps the old /laender/thueringen/ URL
shape from the previous site (bookmarks and SEO depend on it).
Data flow.
data/data_<year>.json → lib/data.ts (decodes UTF-8 / UTF-16 exports, normalizes
messy fields into typed Indicators) → lib/scoring.ts → lib/rankingViews.ts →
components. Scoring is deliberately not a plain points ratio: each category's
percentage is rounded first, then the four rounded percentages are averaged and
rounded again (getTotalFor in lib/scoring.ts, ported 1:1 from the old site).
Years. The ranking has been run for multiple years (YEARS in
lib/constants.ts); DEFAULT_YEAR is what the home page and map show first. Add a
year by dropping in data/data_<year>.json and extending YEARS / the Year type.
State content. Each Bundesland's prose lives in content/states/<slug>.md with
YAML frontmatter (name, status, optional title, ifg_* fields). lib/states.ts
parses these; status: draft marks a state whose write-up isn't finished.
First install Node.js, then run
npm install
npm run devOther scripts: npm run build (static export to out/), npm run start
(serve a production build), npm run lint.
npm run buildThis produces a fully static site in out/, ready to deploy to any static host.
Currently Data is updated by adding a data_YEAR.json file to /data. There is another way to update Data via Google sheets but in order to do so Data would have to be correctly stored there first.
The ranking data lives in a Google Sheet and is pulled into data/data.json on
demand — it is not fetched automatically as part of npm run build, so builds
never depend on Google Sheets being reachable. To refresh it after the sheet has
been updated:
npm run fetch-dataThen commit the resulting data/data.json.
Each Bundesland's descriptive text lives in content/states/*.md as plain Markdown
with YAML frontmatter — edit these files directly and rebuild.