De stack
Een wiki die niet te redden was, laag voor laag gered
2025 tot 2026 · ongeveer 400 uur · meer dan 20 technische rapporten
Eerst bewezen dat het kon, op een kopie, en pas daarna een offerte.
Dezelfde doorsneden, als tekst
Wat elke laag doet, en wat er speelde
atlasmuseum
MediaWiki
het fundament
1.26.4 naar 1.39.10, in drie stappen
De software onder een openbare atlas van kunst in de openbare ruimte in Frankrijk, voor een vereniging met steun van het Franse Ministerie van Cultuur.
Het probleem
De live site was overgenomen door piratenaccounts en draaide op een versie uit 2015 zonder beveiligingsupdates. Een eerdere migratie was halverwege gestaakt en had gemengde en ontbrekende tabellen achtergelaten. Meer dan één partij zei: niet te redden.
De oplossing
Eerst een gratis haalbaarheidstest op een lokale kopie, pas daarna een offerte. Daarna in stappen omhoog: 1.31, 1.35 en 1.39.10. Tienduizenden spampagina's en accounts verwijderd, de verwijderlijst drie keer nagelopen.
Voor developers
MediaWiki 1.26.4 naar 1.31 naar 1.35 naar 1.39.10, met het actor-model, de Multi Content Revision-tabellen en de extensiemanifesten van 1.39 onderweg.
bron: opleverdocument atlasmuseum
PHP 8.3
de motor
7.4 naar 8.3.36 · 43 extensies mee
De taal waarin de wiki en al zijn uitbreidingen geschreven zijn. Van versie 7.4 naar 8.3: een nieuwe motor in een auto die blijft rijden.
Het probleem
De collectiepagina werkte onder PHP 8 helemaal niet meer: argumenten in de verkeerde volgorde, een paginering die nooit verder kon komen, en één ongeldige opdracht die het hele verzoek liet crashen. In de kaartgenerator zat een SQL-injectie: invoer van buiten kon als opdracht aan de database worden gelezen.
De oplossing
De collectiepagina regel voor regel hersteld. Het lek gedicht met een prepared statement, zodat invoer nooit meer als opdracht wordt gelezen. Sinds oktober 2026 draait de motor in een eigen container op mijn eigen server, die ik bij elke beveiligingsupdate in drie minuten van nul opnieuw bouw.
Voor developers
collection.php: join() met omgekeerde argumenten, array_merge() op een objectresultaat, een pagineringsplafond van 4.000 onder een paginagrootte van 5.000. map.php::getMap(): SQL-injectie via de parameter nature. Image op php:8.3-fpm-bookworm met mysqli, intl, gd, zip, calendar, exif, opcache en APCu 5.1.28, herbouwd met --pull --no-cache. De versie bovenaan wordt elke tien minuten live uit api.php van de wiki gelezen.
$stmt->bind_param(str_repeat('s', sizeof($nature)), ...array_values($nature));AMAPI, map.php
MySQL 8
het archief
5.7 naar 8.0, ter plekke gerepareerd
Hier staat alles: elke versie van elke pagina. Opnieuw beginnen had de hele geschiedenis gekost.
Het probleem
De database zat vol schade van de afgebroken migratie: dubbele revisienummers, alleen al op één revisie meer dan 8.000 rijen, een ontbrekende index en halve tabellen van nieuwere versies.
De oplossing
Gerepareerd terwijl alles bleef staan, niet opnieuw opgebouwd. Ruim 8.000 rijen hernummerd, de index en de tabellen aangevuld, zodat de volledige geschiedenis bewaard bleef.
Voor developers
Dubbele archive revision ids hernummerd (revisie 149315), ontbrekende revision timestamp-index, actor-migratie, MCR-tabellen, reparaties in change tags, logging, filearchive en user talk.
bron: opleverdocument atlasmuseum
Semantic MediaWiki
de catalogus
51.277 entiteiten, 0 overgeslagen
De laag die weet welke kunstenaar welk werk maakte en waar het staat. Daarmee zoekt en filtert de atlas.
Het probleem
Een onderdeel werd niet geladen. Daardoor stond op 4.589 pagina's bij de kunstenaar niet diens naam, maar de naam van een variabele uit de code.
De oplossing
De oorzaak gevonden, het onderdeel geladen en de hele catalogus opnieuw opgebouwd: 51.277 entiteiten in negen en een halve minuut, nul overgeslagen, afgesloten met exit=0.
Voor developers
PageForms was niet geladen, dus #arraymap klapte nooit uit en SMW sloeg de lusvariabele op (Auteur=Artist) in plaats van de waarde. rebuildData.php onder SMW 5.1.0.
$ php extensions/SemanticMediaWiki/maintenance/rebuildData.php --auto-recoverywiki139/logs/rebuildData-20260909-015400.log
Cache in twee lagen
de snelheid
26 tot 64 keer sneller
Wat al eens is opgezocht, wordt onthouden. De bezoeker wacht niet opnieuw op iets dat het systeem al weet.
Het probleem
De site riep zichzelf via een omweg aan, en externe bronnen zoals Wikidata werden zonder cache bevraagd. Pagina's waren daardoor traag.
De oplossing
De omweg weggehaald, een cache in twee lagen rond de externe aanroepen, de paginacache aan met een epoch om gericht te verversen, en afbeeldingen die pas laden als ze in beeld komen. Herhaalde paginaladingen 26 tot 64 keer sneller, gemeten.
Voor developers
Memoisatie binnen één verzoek plus een schijfcache met een TTL van dertig minuten rond de upstream API en SPARQL; parser- en paginacache met cache-epoch; server-to-self loopback verwijderd; galerijen achter een IntersectionObserver.
bron: opleverdocument atlasmuseum
Uitvaart-SaaS
Next.js en React
24 schermen
4.000 dossiers: 2.100 naar 40 ms
Alles waar een uitvaartondernemer in werkt: het dossier met zeven tabbladen, een weekkalender, vervoer, het draaiboek van de uitvaartdag, inkoop en facturen.
Het probleem
Met 4.000 dossiers in het systeem deden de overzichtsschermen er ongeveer 2.100 milliseconden over.
De oplossing
Paginering en de juiste indexen in de database. Dezelfde schermen laden nu in ongeveer 40 milliseconden, gemeten op 4.000 dossiers.
Voor developers
Lijstschermen van ongeveer 2.100 ms naar ongeveer 40 ms op 4.000 dossiers, door paginering en indexering. Vier productie-dependencies: next, react, react-dom en postgres. Geen ORM, geen CSS-framework, geen authenticatiebibliotheek.
bron: overdracht september 2026
Wetgeving
Wet op de lijkbezorging
termijnen in de database
Een uitvaart moet aan strikte wettelijke termijnen voldoen. Het systeem bewaakt die termijnen zelf, in de database, in plaats van te vertrouwen op wie de planning maakt.
Het probleem
De wet kent harde termijnen: niet eerder dan 36 uur na het overlijden, uiterlijk de zesde werkdag, aparte termijnen voor as, verlofregels, btw-regels, en facturen die na verzenden niet meer mogen veranderen.
De oplossing
Die regels staan in de database zelf in plaats van verspreid door de applicatie. Elk scherm, elke import en elke toekomstige koppeling loopt langs dezelfde regels, op dezelfde plek.
Voor developers
De 36-uurstermijn en de zesde werkdag uit de Wet op de lijkbezorging, verlofregels, de termijn uit artikel 59 voor as, btw-regels en factuurvergrendeling, vastgelegd in de database in plaats van in applicatiecode.
bron: overdracht september 2026
PostgreSQL
plain SQL
46 tabellen · 119 SQL-tests
De gegevens van alle uitvaartondernemers. De database test zichzelf: 119 tests controleren of de regels doen wat ze moeten doen.
Het probleem
Als de regels in de database zitten, moeten ze ook daar getest worden. Een test in de applicatie bewijst niets over een regel die in de database draait.
De oplossing
119 tests die rechtstreeks in de database draaien, over 46 tabellen en 29 genummerde migraties.
Voor developers
29 SQL-migraties, 46 tabellen, 119 SQL-tests, ongeveer 19.300 regels code bij de overdracht.
bron: overdracht september 2026
Row-level security
multi-tenant
FORCE en een RESTRICTIVE rolslot
Alle uitvaartondernemers werken in hetzelfde systeem, elk op een eigen subdomein. Ieder heeft een eigen kluis in hetzelfde gebouw.
Het probleem
In software voor meerdere uitvaartondernemers is één vergeten filter in een query genoeg om dossiers van een andere klant te tonen.
De oplossing
Row-level security op elke tabel, met FORCE en een RESTRICTIVE rolslot. Een query die zijn filter vergeet, krijgt niets terug in plaats van andermans dossiers. De database beschermt, niet de discipline van de programmeur.
Voor developers
RLS op elke tabel met FORCE ROW LEVEL SECURITY en een RESTRICTIVE policy op de applicatierol, zodat een query zonder tenantfilter een lege set geeft.
bron: overdracht september 2026
Auditlog
alleen toevoegen
oude en nieuwe waarde, altijd
Alles wat er in een dossier verandert, wordt vastgelegd met wat er stond en wat het werd. Daarmee is een vergissing terug te draaien.
Het probleem
Bij uitvaartdossiers moet elke wijziging herleidbaar zijn, en een vergissing van een klant moet te herstellen zijn.
De oplossing
Een auditlog waar alleen aan kan worden toegevoegd, met de oude en de nieuwe waarde van elke wijziging. Hetzelfde log drijft de functie ongedaan maken.
Voor developers
Append-only auditlog met oude en nieuwe waarden; de undo-functie leest uit hetzelfde log.
bron: overdracht september 2026
Datacenter, Naaldwijk
de hosting
ISO 27001 · ISO 9001 · ISO 14001 · NEN 7510
De servers staan in een Nederlands datacenter waarvan de beveiliging elk jaar door een onafhankelijke partij wordt gecontroleerd.
Het probleem
Uitvaartdossiers bevatten persoonsgegevens van overledenen en nabestaanden. Een klant wil weten waar die staan en wie daarop let.
De oplossing
Lancering in een datacenter in Naaldwijk dat gecertificeerd is volgens ISO 27001, ISO 9001, ISO 14001 en NEN 7510. Dat zijn certificeringen van het datacenter, niet van de software zelf.
Voor developers
Het datacenter heeft ISO 27001 (informatiebeveiliging), ISO 9001 (kwaliteit), ISO 14001 (milieu), NEN 7510 (informatiebeveiliging in de zorg), SOC 1 en SOC 2, en PCI DSS. De software zelf scheidt klanten met row-level security in de database.
bron: certificeringen van het datacenter, opgehaald 17 oktober 2026
Deze site
React
de interface
werkt ook zonder JavaScript
Het scherm waarop een bezoeker typt en klikt. Het contactformulier werkt ook zonder JavaScript, dus er valt niemand af.
Het probleem
Op een iPhone was de pagina 16 pixels breder dan het scherm. Safari zoomde daardoor uit, de pagina schoof opzij en rechts ontstond een donkere strook.
De oplossing
De oorzaak gevonden in een menurij en een vaste achtergrond die over de rand staken. Opgelost, en de hele site kreeg overflow-x: clip als vangnet, zonder de sticky scènes te breken.
Voor developers
Het formulier is een echte form met een echte action; de client-state toont alleen de voortgang. overflow-x: clip in plaats van hidden, omdat hidden de root tot scrollcontainer maakt en position: sticky breekt.
const [submitting, setSubmitting] = useState(false);apps/web/src/components/contact-form.tsx:40
Next.js
App Router
10 pagina's · 3 API-routes
Als de receptie van een kantoor: neemt het bericht aan, kijkt of alles is ingevuld en bevestigt meteen.
Het probleem
De build op de server faalde zodra Google Fonts even niet bereikbaar was. De site kon dan niet worden bijgewerkt omdat een andere dienst haperde.
De oplossing
De lettertypes meegeleverd in plaats van bij Google opgehaald. De build heeft geen externe dienst meer nodig.
Voor developers
next/font/google vervangen door next/font/local met de woff2-bestanden en hun OFL-licenties in de repo, zodat de build hermetisch is.
export async function POST(request: Request): Promise<Response> {apps/web/src/app/api/v1/enquiries/route.ts:55
Node.js
de worker
nooit twee keer dezelfde rij
Twee postbodes werken tegelijk en pakken nooit dezelfde brief. Er gaat geen bericht verloren en er komt er geen dubbel aan.
Het probleem
Twee workers draaien tegelijk. Zonder afspraak kunnen ze hetzelfde bericht pakken, of halverwege stoppen en het bericht laten liggen.
De oplossing
Elke worker claimt een rij en neemt hem vijf minuten in huur. Stopt hij onderweg, dan komt de rij daarna vanzelf weer vrij.
Voor developers
UPDATE … WHERE id IN (SELECT … FOR UPDATE SKIP LOCKED) met een lease op notify_after van vijf minuten, in één statement.
FOR UPDATE SKIP LOCKEDpackages/db/src/enquiries.ts:108
PostgreSQL
17 · plain SQL
4 migraties · geen ORM
Het archief waar alles in staat. Elke wijziging is vastgelegd als een genummerd bestand, zoals notulen.
Het probleem
Als de mailserver plat ligt, mag geen aanvraag verloren gaan, en de bezoeker mag er niets van merken.
De oplossing
De aanvraag wordt eerst opgeslagen; mailen is een losse stap erna. Mislukt die, dan volgt een nieuwe poging na één minuut, steeds dubbel zo lang tot zes uur, maximaal twaalf keer, met de foutmelding bij de rij.
Voor developers
Transactionele outbox op één tabel: notify_attempts, notify_after en notify_error, met een backoff die bij één minuut begint en verdubbelt tot zes uur.
ADD COLUMN notify_attempts integer NOT NULL DEFAULT 0,packages/db/migrations/0004_enquiry_notification.sql:12
Hashketen
het bewijs
elk record vast aan het vorige
Elke meting van deze site krijgt een zegel dat aan de vorige vastzit. Wie er later iets aan verandert, breekt de keten, en dat kunt u in uw eigen browser nakijken.
Het probleem
Een getal op een website is niets waard als de eigenaar het achteraf stil kan aanpassen.
De oplossing
Elke meting wordt gehasht samen met de hash van de vorige. Eén gewijzigd record maakt alle volgende hashes ongeldig, op een aanwijsbare plek.
Voor developers
hash = sha256(utf8(prev_hash_hex) || utf8(canonical_json(body))), met canonieke JSON volgens RFC 8785, zodat dezelfde meting altijd dezelfde hash geeft.
return Sha256.unsafe(sha256Hex(concatBytes(utf8(prevHash), utf8(canonical))));packages/core/src/chain/hash-chain.ts:88
Logo's: Simple Icons (CC0), naar de merkpagina's van de eigenaars. De overige tekens zijn eigen werk.