Context & probleem
Een portfolio kan makkelijk eindigen als een statische brochure: snel online, maar technisch weinigzeggend. Voor jaridata.dev is bewust het omgekeerde gekozen. De site moet laten zien hoe ik software, data en cloudwerk structureer: kleine onderdelen, expliciete contracten, reproduceerbare builds en duidelijke guardrails.
Dat betekent niet dat elke persoonlijke site een zwaar platform moet worden. Hier is de extra techniek functioneel: de site bevat meertalige routes, case-content, SEO-metadata, themegedrag, security headers, edge/origin-afscherming en deployment-infrastructuur. Dat zijn precies de onderdelen waarop productie-apps meestal rommelig worden als ze later pas worden toegevoegd.
De vraag was dus niet: "hoe maak je een mooie homepage?", maar: "hoe bouw je een portfolio dat dezelfde engineeringdiscipline laat zien als de projecten die het beschrijft?"
Architectuur
De repository is opgezet als een kleine server-rendered applicatie:
Application
FastAPI routes → Jinja2 templates → locale-aware SSR pages
Content
cases.py + i18n.py → portfolio cards, case pages, chrome copy
Frontend
tokens.css → components/pages CSS → vanilla JS enhancements
Delivery
npm build:css → Docker image → GitHub Actions → Terraform → Cloud Run
FastAPI levert de routes, Jinja2 rendert HTML op de server en de contentlaag voedt templates met expliciete NL/EN-records. Static assets worden met een versie-query geladen en immutable gecachet. De Cloud Run-infrastructuur staat in Terraform; GitHub Actions draait eerst kwaliteitspoorten voordat de image gebouwd en uitgerold wordt.
Applicatielaag
De route-opbouw staat centraal in app/api/routers/pages.py. Eén router-factory bouwt de Nederlandse canonical routes en de Engelse /en/*-routes, zodat handlers niet per taal kunnen divergeren.
Deep-dive cases worden vanuit app/content/cases.py bepaald. Als deep_dive=True, verwacht de template bewust twee includes: <slug>.nl.html en <slug>.en.html. Er is geen stille fallback naar Engels; ontbrekende content moet hard falen.
SEO is onderdeel van dezelfde applicatielaag. template_response() injecteert canonical en hreflang-data, terwijl meta.py robots.txt, sitemap.xml en /healthz serveert.
De site heeft geen losse locale-sites, geen client-side taalwissel en geen sitemap die met de hand wordt bijgewerkt. Routes, cases en SEO-metadata komen uit dezelfde brondata.
Frontend- en designsysteem
De frontend is bewust zonder client-framework gebouwd. Jinja levert de HTML; JavaScript voegt interactie toe waar dat waarde heeft: theme-toggle, mobiele navigatie, portfolio peek-panel, terminalgedrag, reveal-effecten en case-lightboxes.
app/static/css/src/tokens.css is de tokenlaag. Dark mode gebruikt de Retro Developer Terminal-richting; light mode volgt de Polar Blueprint-richting. Die twee thema's hoeven niet dezelfde esthetiek te delen, maar ze moeten wel allebei vanuit tokens en expliciete light-mode selectors worden aangestuurd.
Casepagina's en architecture visuals gebruiken bestaande classes zoals .case-main--cat-* en .case-archviz. Daardoor blijven accenten, lightbox-gedrag, reduced-motion en light/dark overrides in het bestaande CSS-contract.
Contentarchitectuur
app/content/cases.py is de single source of truth voor portfolio-cards: slug, categorie, bento-formaat, titel, blurb, TL;DR, stack, metrics, deep-dive status en architecture-flag. Localevelden zijn dictionaries met verplichte nl en en waarden.
Korte chrome-copy staat in app/content/i18n.py. Lange case-tekst leeft in Jinja-includes per locale. Tests controleren dat deep-dive includes bestaan, dat sectie-aantallen gelijk blijven en dat macrogebruik tussen NL en EN niet ongemerkt uit elkaar loopt.
Nieuwe cases schalen daardoor zonder nieuwe routecode: datarecord toevoegen, twee locale-includes schrijven en optioneel een SVG-partial koppelen voor de architecture view.
Deployment & operations
De Dockerfile heeft een Node-builderfase voor CSS en image-optimalisatie, gevolgd door een Python-runtime met uv. De runtime start Uvicorn op poort 8080, passend bij Cloud Run.
Terraform beheert de Cloud Run-service, service account, Cloudflare-resources en monitoringconfiguratie. De Cloud Run-module zet health probes, min_instance_count=0, max_instance_count=1, request-concurrency en environment variables zoals APP_VERSION, CLOUD_REGION en CLOUDFLARE_ONLY.
GitHub Actions voert een infra-plan, lint, format-check, mypy en coverage-tests uit voordat de deploy-job bouwt, pusht en een Terraform plan/apply uitvoert. De post-deploy smoke test verwacht bewust een 403 op de directe run.app-URL, zodat origin-bypass via CloudflareOnly fail-closed blijft.
AI-agent guardrails
De repository bevat expliciete regels voor AI-assisted development. AGENTS.md, DESIGN.md, CLAUDE.md, GEMINI.md en app/static/README.md leggen vast hoe agents moeten werken met privacy, i18n, CSS-tokens, theme-splitsing, frontend-validatie en audit-output.
Voor theme- en frontendwerk is de trace verplicht: tokens → CSS-consumers → templates → Python-content → JavaScript runtime styles → SVG-classes → routes → browservalidatie. Dat is bewust streng, omdat kleine kleur- of specificity-fixes anders makkelijk dark mode, light mode of CSP breken.
De docs beschrijven niet alleen intentie. Ze leggen concrete verboden en checks vast, zoals geen directe environment reads in app/, nonce op inline scripts/styles, geen inline style-attributen voor CSP-gevoelige elementen en IaC-first voor infrastructuur.
Engineeringbeslissingen die ertoe doen
- SSR boven SPA. De site is content- en SEO-zwaar; FastAPI/Jinja2 levert daarvoor minder runtime-complexiteit dan een client-heavy app.
- Vanilla JS waar het genoeg is. Theme, navigatie, terminal en case-interactie hebben geen framework nodig.
- Tokens als contract. Kleur en surface-gedrag horen in CSS-variabelen, niet verspreid door templates of JavaScript.
- Content los van rendering. Portfolio metadata leeft in Python-data; long-form copy leeft in locale-includes.
- Asymmetrische thema's. Dark en light mode delen componenten, maar niet blind dezelfde esthetiek.
- IaC en CI als basis. De persoonlijke site gebruikt dezelfde reviewbare build- en deploygewoontes als productieplatformen.
Resultaat
jaridata.dev bewijst vooral werkwijze. De site laat zien dat ik niet alleen data/cloud/AI-architectuur beschrijf, maar ook mijn eigen publieke platform met dezelfde discipline bouw.
- server-rendered FastAPI/Jinja2 applicatie
- NL/EN routes met canonical en hreflang
- content-gedreven portfolio-cases
- token-based dark/light designsysteem
- CSP, security headers en Cloudflare-only origin protection
- immutable static caching met versie-query's
- Docker- en Cloud Run-deploymentpad
- Terraform, GitHub Actions en testbare guardrails
Status
De huidige site is live als productie-achtige portfolio-app. De code bevat routes, templates, i18n, SEO, middleware, tests, Docker, Terraform en CI/CD-context. Tegelijk zijn sommige commerciële functionaliteiten bewust nog niet actief: de contactpagina is een placeholder en functionele data/AI API-endpoints staan volgens de README op de roadmap, niet in de huidige app.