Numerus
Numerus is onze eigen Romeinse-cijferconverter op romannumerals.app: type een getal of een cijfer en lees direct het antwoord. Daaromheen ligt een naslagwerk van per-getal- en per-jaarpagina's, charts, een regelgids en een datumgenerator. De conversie zelf gebeurt in de browser, zonder backend, database of account.
Bekijk live →
De uitdaging
"Roman numerals" is een zoekterm met bijna een half miljoen zoekopdrachten per maand in de VS (Ahrefs: 496.000, KD 66) en een top 10 die vol staat met hoog-autoritaire calculatorsites. Die head-term winnen we niet, en dat is ook niet het punt: iemand die "47 in roman numerals" intikt wil één regel antwoord en krijgt in de praktijk vaak een advertentiemuur, een cookiebanner en een pagina die leest als een template met een omgewisselde variabele. De opgave was dus niet een converter bouwen, maar een naslagwerk maken dat honderden long-tail-vragen sneller en eerlijker beantwoordt dan wat er al stond.
Onze aanpak
We hebben de zoekvraag eerst uit elkaar getrokken met Ahrefs en die analyse laten bepalen welke pagina's überhaupt mochten bestaan: 121 getalpagina's, 200 jaarpagina's (1900 tot 2099) en 6 charts, met één primair zoekwoord per pagina zodat de set zichzelf niet kannibaliseert. Daaromheen bouwden we een eigen design-systeem in kale CSS (klassiek perkament en oxbloed, Cormorant Garamond als display-letter) en een pure conversiemodule in TypeScript die in de browser draait. De hele site wordt statisch geprerenderd, inclusief 3.999 JSON-API-responses en 322 Open Graph-afbeeldingen.
Het resultaat
De build levert 4.668 vooraf gerenderde routes op: 3.999 statische API-responses, de 341 indexeerbare pagina's uit de sitemap, 322 Open Graph-afbeeldingen en zes systeemroutes zoals robots.txt, de sitemap en de widget. Tijdens het converteren berekent geen server iets mee, er is geen tracking en standaard geen advertenties. Twee eigen controlescripts bewaken de kwaliteit: de ene faalt de build op gebroken interne links of weespagina's, de andere leest de JSON-LD van elke gebouwde pagina in en faalt op ongeldige of ontbrekende blokken.
Resultaat
341 pagina's in de sitemap, 3.999 statische API-responses, converter volledig client-side
Waarom uitgerekend Romeinse cijfers
Het is een van de meest gestelde vragen op het open web en tegelijk een van de slechtst beantwoorde. Iemand ziet MMXXIV op een gevel, een aftiteling of een tatoeage en wil weten wat er staat. Dat is een vraag met één juist antwoord, in één regel te geven. Wat de zoeker in de praktijk krijgt is een pagina vol advertentieblokken, een cookiedialoog en een rekentool die pas na een paar seconden bruikbaar is. De afstand tussen de eenvoud van de vraag en de rommeligheid van het antwoord was voor ons het hele argument om deze niche te kiezen.
De cijfers uit onze Ahrefs-analyse (VS, juli 2026) bevestigden dat de kans niet bovenaan ligt. De term "roman numerals" doet 496.000 zoekopdrachten per maand bij een keyword difficulty van 66, en zelfs de zwakste concurrent in de top 10 heeft nog een domain rating van 26. Die strijd voeren we niet. De staart daarentegen is zacht: "xxiv roman numerals" staat op KD 14 met 3.500 zoekopdrachten, "40 in roman numerals" op KD 15, en "roman numerals tattoo" (8.000) en "2026 in roman numerals" (3.900) staan zelfs op KD 0. Daar is met echte kwaliteit gewoon te winnen.
Dat leverde ook meteen het publiek op waar we voor ontwerpen: scholieren en docenten die de regels willen begrijpen, en mensen die een datum zoeken voor een tatoeage, een trouwring of een gedenksteen. Dat zijn twee heel verschillende bezoekers met dezelfde onderliggende vraag. De eerste wil weten waarom XLIX zo geschreven wordt, de tweede wil zijn eigen datum netjes in beeld zien. Numerus moest beide bedienen zonder er een compromis van te maken.
Een merk in steen, niet in een template
Numerus heeft een eigen design-systeem. Geen thema, geen Tailwind, geen componentbibliotheek: handgeschreven CSS, met een compacte set custom properties als enige bron van waarheid voor kleur, ruimte en schaduw. Licht en donker vullen diezelfde set allebei in, en de rest van het bestand verwijst er alleen nog naar. De hele site draait op drie runtime-afhankelijkheden, next, react en react-dom, en verder niets. Dat is een bewuste keuze en geen koppigheid. Een tool die maar één ding doet, verdient een uiterlijk dat bij dat ene ding hoort, en dat krijg je niet uit een raamwerk dat voor alles tegelijk is bedacht.
De visuele richting volgt het onderwerp letterlijk. De achtergrond is warm perkament (#f4efe6), het accent is oxbloed (#7c2d2d) met antiek goud (#a9822f) ernaast, en de display-letter is Cormorant Garamond: een verfijnde schreefletter die de cijfers laat lezen als een inscriptie in plaats van als output van een rekenmachine. Geen blauw, geen standaard SaaS-look. Het geheel voelt gegraveerd, wat ook is wat een Romeins cijfer is. Het donkere thema hangt aan datzelfde variabelenstelsel, via een data-attribuut op de html-tag dat een inline script vóór de eerste verf zet, dus zonder flikkering.
De typografie draagt ook de rest van de UI. De letters worden bij de build zelf gehost via next/font/google, dus het bezoek hangt niet aan een lettertype-CDN. Toegankelijkheid zit in dezelfde laag: semantische HTML, een skip-link naar de inhoud, zichtbare focusstijlen op het oxbloed-accent en contrastverhoudingen die in beide thema's op WCAG AA mikken.
Eén functie, driehonderd pagina's
De converter is het hart, maar de site is groter dan de tool. Rond de homepage ligt een programmatische naslagstructuur: 121 getalpagina's (1 tot en met 100, plus ronde en mijlpaalwaarden tot 3999), 200 jaarpagina's die het volledige bereik 1900 tot 2099 dekken, 6 charts (van 1 tot 10 tot en met de complete tabel 1 tot 1000), een regelgids, een printbare chart, een tatoeagegids en een datumgenerator. Met de hub- en informatiepagina's erbij zijn dat 341 URL's in de sitemap.
Programmatisch betekent hier niet dat de tekst uit een mal komt. De vijftig zwaarst gezochte pagina's (18 getallen en 32 jaren) hebben een met de hand geschreven introductie die uitgaat van een echt feit: vier legt uit waarom klokkenmakers de regel breken en toch IIII graveren, negenennegentig waarom het XCIX is en geen IC, 1999 waarom drie subtracties op rij de overgang naar MM als een opluchting lieten voelen. De overige pagina's krijgen een generator die de openingszin kiest op grond van wat er werkelijk waar is over dat getal of jaar: gebruikt het de subtractieve regel, is het een schrikkeljaar, opent het een decennium of een eeuw, hoe ver ligt het van nu af. Dat is geen handwerk, en we doen ook niet alsof, maar het is wel iets anders dan één zin met een gewisselde variabele.
Die lijn loopt door in de rest van de pagina. Een jaarpagina leidt zijn gebruikscontexten af uit het jaar zelf: de eeuwband die de openingsglyphs bepaalt (MCM voor de twintigste eeuw, MM daarna), de filmconventie om het copyrightjaar in Romeinse cijfers op de aftiteling te zetten, en een jubileumsom die tegen het huidige jaar wordt uitgerekend. De FAQ werkt net zo en vangt de twee jaren op die hun eigen band zijn: bij 1900 en 2000 zou de vraag "waarom begint MM met MM" onzin zijn, dus die krijgen een andere formulering. De interne links volgen dezelfde redenering. Een strikte plus-één-min-één-wandeling vanaf 1975 zou alle acht de links besteden binnen 1971 tot 1979, waar nauwelijks vraag zit. Numerus houdt de directe buren (1974 en 1976 staan gewoon vooraan, want "en het jaar erna?" is een echte vervolgvraag) en geeft de rest aan de decennium-ankers en de jubileumafstanden: 1970, 1980, 1960, 2000, 1950 en 2025. De actuele jarenband staat achteraan in de rij kandidaten, dus die haalt de lijst alleen waar er ruimte over is.
Statisch tot in de laatste JSON-response
De stack is Next.js 16.2.9 met de App Router, React 19.2.4 en TypeScript in strict mode. Er is geen backend en geen database, en de tool zelf doet tijdens het gebruik geen netwerkverzoek. De conversielogica van de site staat in één afhankelijkheidsvrije module (lib/roman.ts) die de standaard subtractieve notatie van 1 tot 3999 implementeert, en daar houdt het ook op: de converter, de API en de widget hanteren alle drie diezelfde bovengrens, en wie een groter getal intikt krijgt de melding dat standaard Romeinse cijfers bij 3999 stoppen. Bij het teruglezen van een cijfer valideert de parser via een round-trip: IIII en VX worden geweigerd, want ze zijn geen canonieke schrijfwijze. Voor de bezoeker betekent dat het meest onderschatte voordeel van deze aanpak: wat je in de converter typt verlaat je apparaat niet, want er is niets om het naartoe te sturen. Het contactformulier is de enige uitzondering, dat post zijn bericht naar één gedeeld endpoint.
De build prerendert alles wat er is: 4.668 routes in totaal. Daar zitten niet alleen de 341 pagina's in, maar ook 3.999 losse JSON-responses, één voor elk getal van 1 tot en met 3999. Dat is de gratis API op /api/roman/[n], die het cijfer, de uitgeschreven vorm en de glyph-opbouw teruggeeft, met CORS open en een cache-header van een jaar. Statisch gegenereerd, dus het is technisch gezien geen API maar een verzameling bestanden die zich als API gedraagt. Ook daar zit een detail in dat het verschil maakt: het source-veld verwijst alleen naar een eigen pagina als die bestaat, en anders naar de converter, zodat de API geen dode URL's de wereld in stuurt.
In dezelfde geest staat de embeddable widget, die als één zelfstandig HTML-document met vanilla JavaScript wordt geserveerd en teruglinkt naar Numerus. Die draagt bewust zijn eigen, ingesloten conversielogica, inclusief dezelfde round-trip-controle. Dat is een tweede implementatie van iets wat al bestond, en dus een echte prijs, maar het alternatief was een frameworkbundel meesturen naar elke site die het ding in een iframe zet. De datumgenerator tekent op een canvas in de browser en levert een downloadbare afbeelding op. Het advertentiecomponent en de consent-banner die in de codebase klaarstaan renderen allebei niets: ze staan achter een omgevingsvariabele die uit staat, en de advertentieplek reserveert straks een vaste hoogte zodat een eenheid de layout niet laat verspringen.
Vindbaarheid als bouwbesluit
Elke pagina draagt zijn eigen titel met het zoekwoord vooraan, een eigen meta-omschrijving en een expliciete canonical. De strengste regel zit een laag dieper: één primair zoekwoord per pagina, en dat is niet vrijblijvend opgeschreven maar in de dataset afgedwongen. De ronde getallen die ook een jaar zijn (1900, 2000, 2020, 2024, 2025) zijn uit de getallenset geknipt, omdat de jaarpagina's die zoekwoorden bezitten. Zo kunnen twee pagina's uit onze eigen set niet om dezelfde zoekvraag vechten. De JSON-LD is opgedeeld naar paginatype: WebApplication op de tool, een @graph van FAQPage plus BreadcrumbList op de pSEO-pagina's en HowTo op de regelgids. Sitemap en robots.txt worden gegenereerd uit één centrale SITE_URL-constante, dus een domeinwissel loopt overal doorheen. Er ligt ook een llms.txt die de API en de paginastructuur documenteert voor taalmodellen.
Twee details laten zien hoe ver dat is doorgedacht. De sitemap gebruikt bewust een vaste inhoudsdatum in plaats van de builddatum: een lastModified die bij elke rebuild verandert terwijl de inhoud gelijk blijft, leert crawlers juist om het signaal te wantrouwen. En omdat Next.js het openGraph-object per route-segment vervangt in plaats van samenvoegt, zou het merendeel van de pagina's stilletjes zijn og:image en og:type verliezen. Daar staat een gedeelde helper voor die dat opvangt. De Open Graph-afbeeldingen zelf worden gerenderd met next/og: 322 stuks, voor de homepage en voor elke getal- en jaarpagina. De overige pagina's krijgen bewust geen eigen beeld en delen de hoofdafbeelding via diezelfde helper, want een unieke afbeelding voor de privacyverklaring levert niemand iets op.
Dat het klopt, controleren we niet met het oog. Twee eigen scripts draaien mee in npm run verify. Het eerste leest de gebouwde HTML-pagina's, verzamelt de interne links en faalt de build zodra er één naar een route wijst die nooit gebouwd is, of zodra een echte inhoudspagina nul inkomende links heeft en dus een weespagina is. Het tweede leest de JSON-LD van elke gebouwde pagina in, faalt op ongeldig JSON en controleert per paginatype of het verwachte schema er staat: WebApplication op de homepage, HowTo op de regelgids, FAQPage en BreadcrumbList op de getal-, jaar- en chartpagina's. Kapotte interne links en ontbrekende structured data komen hier niet langs, want dan komt de build er niet doorheen.
Waar het nu staat, en wat het laat zien
Numerus is in juli 2026 gebouwd en draait op romannumerals.app. De build en beide verificatiestappen komen schoon door. Het is een van de 28 organische tool-sites die we volledig zelf hebben bedacht, ontworpen, gebouwd en onderhouden, zonder opdracht en zonder actief verdienmodel. Er zit geen aanmelding op, geen tracking en standaard geen advertentie. De enige hefboom is kwaliteit, en dat is ook waarom het een eerlijke proef is: er is geen budget dat het resultaat kan maskeren.
Voor ons is dit soort werk het bewijsmateriaal dat we bij een echte opdracht niet kunnen improviseren. Het zoekwoordonderzoek dat bepaalt welke pagina's mogen bestaan, het merk dat uit het onderwerp is afgeleid in plaats van uit een template, de conversielogica die als pure functie te lezen en te testen is, de vindbaarheid die als bouwbesluit in de code zit in plaats van als plugin erbovenop: dat is één keten, van strategie tot deploy, en wij houden hem hier volledig in eigen hand.
De site is jong en het domein moet zijn autoriteit nog opbouwen. Dat is het eerlijke antwoord op de vraag hoe het loopt. Wat er wel staat is een fundament dat meeschaalt: een groeipad naar ruim 500 pagina's ligt uitgeschreven klaar, de jarenuitbreiding naar 1900 tot 2099 is er doorheen gegaan door één array te verbreden waar de generatie, de sitemap en de chart allemaal uit lezen, en de interne linkstructuur wordt inmiddels bijgestuurd op wat Search Console over de echte zoekvraag laat zien. De architectuur is er op gebouwd om dat te kunnen.
Klaar om jouw merk te laten groeien?
Vertel ons over je project. We denken graag met je mee, vrijblijvend.
Vrijblijvend. Je hoort binnen één werkdag van Jamey.