SEO Metadata Generator (NL) – Titel, Beschrijving, Slug & Tags
Een goede pagina kan inhoudelijk uitstekend zijn en toch nauwelijks verkeer krijgen, simpelweg omdat de SEO-metadata niet optimaal is. Metadata is de “verpakking” waarmee zoekmachines (en gebruikers in de zoekresultaten) begrijpen waar je pagina over gaat. In deze tutorial bouw je een SEO Metadata Generator die voor Nederlandstalige content automatisch:
- een SEO-titel (title tag) voorstelt,
- een meta description schrijft,
- een slug maakt (URL-pad),
- tags/keywords suggereert,
- en optioneel varianten genereert voor A/B-tests.
Je krijgt diepe uitleg, plus echte commands om alles lokaal te draaien. We gebruiken Node.js en een simpele CLI, zodat je het kunt integreren in je workflow (CMS, Markdown-site, e-commerce, etc.).
Inhoudsopgave
- Wat zijn SEO-metadata en waarom zijn ze belangrijk?
- SEO-titel: regels, valkuilen en best practices
- Meta description: hoe schrijf je een goede beschrijving?
- Slug: leesbaar, stabiel en zoekmachinevriendelijk
- Tags/keywords: nuttig, maar met nuance
- Ontwerp van de generator: input, output en kwaliteitsregels
- Project opzetten (Node.js CLI)
- Implementatie: titel, description, slug en tags genereren
- Kwaliteitschecks: lengte, duplicaten, stopwoorden, CTR-signalen
- Gebruik in de praktijk: voorbeelden en commands
- Integratie in een content-pipeline (Markdown, Git, CI)
- Veelgemaakte fouten en hoe je ze voorkomt
- Uitbreidingen: meervoudige varianten, SERP-preview, interne linking hints
Wat zijn SEO-metadata en waarom zijn ze belangrijk?
SEO-metadata zijn velden die niet (altijd) prominent zichtbaar zijn op de pagina zelf, maar wel door zoekmachines en sociale platforms worden gebruikt om je pagina te begrijpen en te presenteren.
De belangrijkste velden:
- Title tag (SEO-titel): de klikbare titel in Google. Heeft sterke invloed op relevantie en klikratio (CTR).
- Meta description: korte samenvatting onder de titel. Geen directe rankingfactor, maar wel CTR-beïnvloeder.
- Slug: het URL-gedeelte na het domein, bijvoorbeeld
/seo-metadata-generator. Helpt bij duidelijkheid, deelbaarheid en soms relevantiesignalen. - Tags/keywords: in moderne SEO minder “magisch” dan vroeger, maar nuttig voor interne organisatie, faceted navigation, contentclusters en suggesties.
Waarom een generator?
- Consistentie: elke pagina krijgt metadata volgens dezelfde regels.
- Snelheid: redacties besparen tijd.
- Kwaliteit: automatische checks op lengte, duplicatie en “spammy” patronen.
- Schaal: ideaal voor grote sites met veel pagina’s (blogs, shops, kennisbanken).
SEO-titel: regels, valkuilen en best practices
Doel van de SEO-titel
De SEO-titel moet tegelijk:
- Relevantie signaleren (zoekmachine begrijpt onderwerp),
- Klikken uitlokken (gebruiker kiest jouw resultaat),
- Merk/vertrouwen uitstralen (optioneel je merknaam).
Lengte: pixels vs. karakters
Google toont titels op basis van pixelbreedte, niet strikt op karakters. Toch kun je in tooling vaak met een richtlijn werken:
- Richtlijn: 50–60 tekens (incl. spaties) als veilige zone.
- Soms kan 65 tekens ook nog, maar het risico op afkappen stijgt.
Structuren die vaak werken
- Primair zoekwoord + belofte/voordeel
SEO Metadata Generator: Titel, Beschrijving & Slug (NL) - How-to / gids
SEO-titel en meta description schrijven: complete gids (NL) - Lijst/format
10 voorbeelden van meta descriptions die klikken opleveren
Valkuilen
- Alleen zoekwoorden stapelen:
SEO titel SEO description SEO slug SEO tags
Dit leest slecht en kan spammy overkomen. - Te generiek:
HomeofBlogzegt niets. - Titel ≠ H1: je H1 mag langer/anders zijn; de title tag is een SERP-asset.
Meta description: hoe schrijf je een goede beschrijving?
Wat doet de meta description?
De meta description is primair een marketingtekst in de SERP. Google kan soms een andere snippet tonen, maar een goede description helpt vaak bij:
- hogere CTR,
- betere verwachting bij de klik (minder bounces),
- duidelijkheid over wat de pagina biedt.
Lengte
- Richtlijn: 120–160 tekens
Op mobiel kan het variëren; soms toont Google meer of minder.
Inhoudelijke checklist
Een sterke description bevat meestal:
- Onderwerp (waar gaat het over?)
- Voordeel (wat levert het op?)
- Concrete elementen (templates, stappenplan, tools)
- Call-to-action (subtiel): “Lees”, “Ontdek”, “Download”
Voorbeeld:
Genereer automatisch SEO-titels, meta descriptions, slugs en tags in het Nederlands. Inclusief kwaliteitschecks en CLI-commands. Start direct.
Valkuilen
- Duplicaten: honderden pagina’s met dezelfde description.
- Te veel superlatieven zonder inhoud: “Beste ooit, geweldig, uniek”.
- Geen match met de pagina: CTR kan stijgen, maar bounces ook.
Slug: leesbaar, stabiel en zoekmachinevriendelijk
Een slug is het pad in de URL, vaak gebaseerd op de titel.
Regels voor goede slugs
- Kleinletters
- Koppeltekens (
-) i.p.v. spaties of underscores - Geen stopwoorden waar mogelijk (de, het, een) – maar overdrijf niet
- Geen speciale tekens (accents, symbolen) → transliteratie
- Stabiel: wijzig slugs niet onnodig (anders redirects nodig)
Voorbeeld:
- Titel:
SEO Metadata Generator (NL) – Titel, Beschrijving, Slug & Tags - Slug:
seo-metadata-generator-nl-titel-beschrijving-slug-tags
Tags/keywords: nuttig, maar met nuance
“Keywords” als meta tag zijn voor Google al jaren irrelevant. Maar tags zijn nog steeds nuttig voor:
- interne zoekfunctie,
- categorieën en contentclusters,
- gerelateerde artikelen,
- navigatie en filters.
Goede tags
- zijn specifiek (niet “marketing” als alles marketing is),
- zijn consistent (zelfde schrijfwijze),
- zijn beperkt in aantal (bijv. 5–12 per pagina),
- bevatten zowel head terms als long-tail varianten.
Voorbeeldtags:
seo metadatameta descriptiontitle tagslug generatortechnische seocontent optimalisatie
Ontwerp van de generator: input, output en kwaliteitsregels
Input
We willen minimaal:
title(werktitel of H1)content(tekst of samenvatting)- optioneel:
brand,tone,audience,maxTitleLength,maxDescLength
Voor CLI is JSON handig, of een tekstbestand.
Output
We genereren een JSON-resultaat:
{
"seoTitle": "...",
"metaDescription": "...",
"slug": "...",
"tags": ["...", "..."],
"checks": {
"titleLength": 58,
"descriptionLength": 154,
"hasCallToAction": true
}
}
Kwaliteitsregels (heuristieken)
- Titel bevat primair onderwerp (meestal eerste woorden)
- Titel niet te lang
- Description bevat minstens 1 voordeel + 1 concreet element
- Slug geen dubbele koppeltekens, geen stopwoorden (optioneel)
- Tags uniek, lowercase, geen rare tekens
Project opzetten (Node.js CLI)
Vereisten
- Node.js 18+ (liefst 20+)
- npm
Controleer:
node -v
npm -v
Nieuwe map en package
mkdir seo-metadata-generator-nl
cd seo-metadata-generator-nl
npm init -y
Dependencies installeren
We gebruiken:
commandervoor CLI-argumentenslugifyvoor nette slugsstopwordvoor stopwoordenfiltering (NL)wink-nlp+ model (optioneel) voor keyword extraction (hier doen we het eenvoudiger met heuristieken; maar ik laat een uitbreidbaar pad zien)
Installeer basis:
npm install commander slugify stopword
Maak een src map:
mkdir src
touch src/index.js
Voeg een bin entry toe aan package.json zodat je het als command kunt draaien:
Open package.json en voeg toe:
"bin": {
"seo-meta-nl": "src/index.js"
}
Maak src/index.js executable (macOS/Linux):
chmod +x src/index.js
Implementatie: titel, description, slug en tags genereren
Plak onderstaande code in src/index.js.
Let op: dit is bewust “plain Node.js” zonder TypeScript, zodat je het direct kunt draaien. Je kunt later altijd migreren.
#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
import { Command } from "commander";
import slugify from "slugify";
import { removeStopwords, nld } from "stopword";
const program = new Command();
function readInput({ file, text }) {
if (file) {
const p = path.resolve(process.cwd(), file);
const raw = fs.readFileSync(p, "utf8");
// Ondersteun JSON of plain text
try {
return JSON.parse(raw);
} catch {
return { title: "", content: raw };
}
}
if (text) return { title: "", content: text };
// stdin fallback
const stdin = fs.readFileSync(0, "utf8");
try {
return JSON.parse(stdin);
} catch {
return { title: "", content: stdin };
}
}
function normalizeWhitespace(s) {
return (s || "").replace(/\s+/g, " ").trim();
}
function truncateAtWordBoundary(text, maxLen) {
const t = normalizeWhitespace(text);
if (t.length <= maxLen) return t;
const cut = t.slice(0, maxLen + 1);
const lastSpace = cut.lastIndexOf(" ");
if (lastSpace < 0) return t.slice(0, maxLen);
return cut.slice(0, lastSpace).trim();
}
function makeSeoTitle({ title, content, brand, maxLen = 60 }) {
const base = normalizeWhitespace(title) || normalizeWhitespace(content).slice(0, 80);
// Heuristiek: voeg (NL) toe als het nuttig is, en brand achteraan
let candidate = base;
// Als titel erg lang is, probeer te comprimeren door stukjes na ":" of "–" te beperken
if (candidate.length > maxLen) {
const parts = candidate.split(/[:–-]\s+/);
if (parts.length > 1) {
candidate = parts[0] + ": " + parts[1];
}
}
// Brand achteraan (met scheidingsteken)
if (brand) {
const withBrand = `${candidate} | ${brand}`;
candidate = withBrand.length <= maxLen + 10 ? withBrand : candidate; // niet forceren
}
candidate = truncateAtWordBoundary(candidate, maxLen);
return candidate;
}
function makeMetaDescription({ title, content, maxLen = 155 }) {
const t = normalizeWhitespace(title);
const c = normalizeWhitespace(content);
// Heuristiek: bouw een description met onderwerp + voordeel + CTA
// We nemen een eerste zin uit content als basis, maar zorgen voor een “marketing” vorm.
const firstSentenceMatch = c.match(/^(.+?[.!?])\s/);
const firstSentence = firstSentenceMatch ? firstSentenceMatch[1] : c;
let desc = "";
if (t) {
desc = `${t}: ${firstSentence}`;
} else {
desc = firstSentence;
}
// Voeg CTA toe als er ruimte is
const cta = " Ontdek de stappen en voorbeelden.";
if ((desc + cta).length <= maxLen) desc += cta;
desc = truncateAtWordBoundary(desc, maxLen);
// Verwijder dubbele punt aan het einde, rare resten
desc = desc.replace(/[:–-]\s*$/, "").trim();
return desc;
}
function makeSlug({ title, content, maxWords = 10 }) {
const base = normalizeWhitespace(title) || normalizeWhitespace(content).slice(0, 120);
// slugify met NL-friendly instellingen
let s = slugify(base, {
lower: true,
strict: true, // verwijdert speciale tekens
locale: "nl"
});
// Stopwoorden verwijderen (optioneel)
const words = s.split("-").filter(Boolean);
const filtered = removeStopwords(words, nld);
const finalWords = (filtered.length ? filtered : words).slice(0, maxWords);
s = finalWords.join("-").replace(/-+/g, "-").replace(/^-|-$/g, "");
return s || "pagina";
}
function extractTags({ title, content, maxTags = 10 }) {
const text = normalizeWhitespace(`${title} ${content}`).toLowerCase();
// Simpele tokenization: letters/cijfers, splits op niet-woord
const tokens = text
.split(/[^a-z0-9à-ÿ]+/i)
.map(t => t.trim())
.filter(t => t.length >= 3);
// Stopwoorden eruit
const filtered = removeStopwords(tokens, nld);
// Frequentietelling
const freq = new Map();
for (const tok of filtered) {
freq.set(tok, (freq.get(tok) || 0) + 1);
}
// Boost woorden uit titel
const titleTokens = removeStopwords(
normalizeWhitespace(title).toLowerCase().split(/[^a-z0-9à-ÿ]+/i).filter(t => t.length >= 3),
nld
);
for (const tok of titleTokens) {
freq.set(tok, (freq.get(tok) || 0) + 2);
}
// Sorteer op score
const sorted = [...freq.entries()]
.sort((a, b) => b[1] - a[1])
.map(([w]) => w);
// Maak tags iets “menselijker”: combineer veelvoorkomende bigrams
// (Heel simpele bigram aanpak)
const words = filtered;
const bigramFreq = new Map();
for (let i = 0; i < words.length - 1; i++) {
const a = words[i], b = words[i + 1];
if (!a || !b) continue;
const bg = `${a} ${b}`;
bigramFreq.set(bg, (bigramFreq.get(bg) || 0) + 1);
}
const bigrams = [...bigramFreq.entries()]
.filter(([, n]) => n >= 2)
.sort((a, b) => b[1] - a[1])
.map(([bg]) => bg);
const combined = [];
for (const bg of bigrams) {
combined.push(bg);
if (combined.length >= Math.floor(maxTags / 2)) break;
}
// Vul aan met single-word tags
for (const w of sorted) {
if (combined.length >= maxTags) break;
if (combined.some(t => t.includes(w))) continue;
combined.push(w);
}
// Uniek + maxTags
const unique = [...new Set(combined)].slice(0, maxTags);
return unique;
}
function runChecks({ seoTitle, metaDescription, slug, tags }) {
const checks = {
titleLength: seoTitle.length,
descriptionLength: metaDescription.length,
slugLength: slug.length,
tagsCount: tags.length,
hasCallToAction: /ontdek|lees|bekijk|download|probeer/i.test(metaDescription),
hasBrandSeparator: /\s[|]\s/.test(seoTitle),
looksSpammyTitle: /(seo\s*){3,}/i.test(seoTitle)
};
return checks;
}
program
.name("seo-meta-nl")
.description("Genereer SEO-titel, meta description, slug en tags (NL).")
.option("-f, --file <path>", "Inputbestand (JSON of tekst)")
.option("-t, --text <text>", "Inputtekst (als alternatief)")
.option("--title <title>", "Titel/H1")
.option("--brand <brand>", "Merknaam (optioneel)")
.option("--max-title <n>", "Max lengte titel", v => parseInt(v, 10), 60)
.option("--max-desc <n>", "Max lengte description", v => parseInt(v, 10), 155)
.option("--max-tags <n>", "Max aantal tags", v => parseInt(v, 10), 10)
.option("--json", "Output als JSON", true);
program.parse(process.argv);
const opts = program.opts();
const input = readInput({ file: opts.file, text: opts.text });
const title = normalizeWhitespace(opts.title ?? input.title ?? "");
const content = normalizeWhitespace(input.content ?? "");
if (!title && !content) {
console.error("Geen input gevonden. Gebruik --file, --text, stdin of --title + content.");
process.exit(1);
}
const seoTitle = makeSeoTitle({
title,
content,
brand: opts.brand,
maxLen: opts.maxTitle
});
const metaDescription = makeMetaDescription({
title,
content,
maxLen: opts.maxDesc
});
const slug = makeSlug({ title, content });
const tags = extractTags({ title, content, maxTags: opts.maxTags });
const result = {
seoTitle,
metaDescription,
slug,
tags,
checks: runChecks({ seoTitle, metaDescription, slug, tags })
};
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
Belangrijke keuzes in de code (uitleg)
- truncateAtWordBoundary: knipt niet midden in een woord, wat professioneler oogt.
- makeSeoTitle: gebruikt titel als basis; anders pakt het een stukje content. Brand wordt alleen toegevoegd als het niet te veel opblaast.
- makeMetaDescription: pakt de eerste zin uit de content, maar zet die in een context met titel en CTA.
- makeSlug: gebruikt
slugifyen haalt stopwoorden eruit. Als alles wegvalt, valt hij terug op de originele woorden. - extractTags: simpele frequentie + titel-boost + bigrammen voor combinaties (“meta description”, “title tag”).
Kwaliteitschecks: lengte, duplicaten, stopwoorden, CTR-signalen
De checks in de output zijn minimale heuristieken. In echte omgevingen wil je vaak extra checks:
1) Duplicatiecontrole (site-breed)
Een generator kan per pagina mooi werk leveren, maar je wil voorkomen dat 200 pagina’s dezelfde titel/description krijgen. Dat vraagt om een index van bestaande metadata.
Praktisch idee:
- Bewaar gegenereerde metadata in een JSONL-bestand (
metadata.jsonl) - Check bij elke run of
seoTitleofmetaDescriptional bestaat
Voorbeeld command om JSONL te maken:
seo-meta-nl --file input.json >> metadata.jsonl
En later duplicaten zoeken (Linux/macOS):
cat metadata.jsonl | jq -r '.seoTitle' | sort | uniq -c | sort -nr | head
2) CTR-signalen
CTR is niet alleen “clickbait”; het gaat om duidelijke waarde:
- cijfers (“7 stappen”, “10 voorbeelden”) werken soms goed,
- woorden als “template”, “checklist”, “tool” zijn concreet,
- maar overdrijf niet.
Je kunt een check toevoegen die kijkt of description minstens één concreet woord bevat, bijvoorbeeld:
stappen,voorbeelden,template,checklist,tool,generator.
3) Stopwoorden en leesbaarheid
Stopwoorden verwijderen uit slugs is vaak goed, maar soms wordt de slug te “telegramstijl”. Overweeg:
- stopwoorden alleen verwijderen als slug > X woorden,
- of alleen de meest overbodige stopwoorden verwijderen.
Gebruik in de praktijk: voorbeelden en commands
1) Draaien via npm link (globale CLI)
In je projectmap:
npm link
Nu is seo-meta-nl beschikbaar.
Test met tekst:
seo-meta-nl --title "SEO Metadata Generator (NL) – Titel, Beschrijving, Slug & Tags" \
--text "In deze tutorial bouw je een generator die automatisch SEO-titels, meta descriptions, slugs en tags maakt. Inclusief kwaliteitschecks en praktische CLI-commands." \
--brand "JouwSite" \
--max-title 60 \
--max-desc 155 \
--max-tags 10
2) Input via JSON-bestand
Maak input.json:
{
"title": "Meta description schrijven: voorbeelden en checklist",
"content": "Een goede meta description verhoogt je klikratio in Google. In deze gids leer je hoe je beschrijvingen schrijft die duidelijk, concreet en aantrekkelijk zijn, inclusief voorbeelden en valkuilen."
}
Run:
seo-meta-nl --file input.json
3) Input via stdin
cat input.json | seo-meta-nl
Of plain text:
echo "Dit is een artikel over slugs en hoe je ze SEO-vriendelijk maakt." | seo-meta-nl --title "SEO-vriendelijke slugs maken"
Integratie in een content-pipeline (Markdown, Git, CI)
Veel teams schrijven content in Markdown. Je kunt metadata genereren tijdens build of pre-commit.
Scenario: Markdown-bestanden in content/
Stel je hebt:
content/artikel-1.mdcontent/artikel-2.md
Je kunt een script maken dat per bestand:
- titel uit eerste
#haalt, - content samenvat (bijv. eerste 1–2 alinea’s),
- generator draait,
- output opslaat in
metadata/.
Snelle aanpak met shell + node
Installeer ripgrep (rg) als je het nog niet hebt (macOS met Homebrew):
brew install ripgrep
Voor Ubuntu/Debian:
sudo apt-get update
sudo apt-get install -y ripgrep
Voorbeeld: titel uit Markdown halen:
rg -m 1 '^#\s+' content/artikel-1.md
Je kunt dit combineren in een klein script, maar dat is een uitbreiding. Het kernidee: metadata is een artefact dat je naast content kunt versioneren.
CI-check: faal build als title te lang is
Je kunt checks.titleLength gebruiken om builds te breken.
Voorbeeld (conceptueel) met jq:
seo-meta-nl --file input.json | jq '.checks.titleLength'
En in bash:
LEN=$(seo-meta-nl --file input.json | jq -r '.checks.titleLength')
if [ "$LEN" -gt 60 ]; then
echo "Titel te lang: $LEN"
exit 1
fi
Veelgemaakte fouten en hoe je ze voorkomt
Fout 1: één template voor alles
Als elke description eindigt met “Lees meer.” zonder inhoud, voelt het generiek. Zorg dat je generator:
- content-specifieke zinnen gebruikt,
- concrete elementen noemt (stappen, checklist, voorbeelden),
- varianten kan maken.
Fout 2: slugs blijven veranderen
Slugs zijn onderdeel van je URL. Wijzigingen betekenen redirects, verlies van “netheid” en risico op 404’s. Oplossing:
- genereer slug alleen bij eerste publicatie,
- sla slug op en hergebruik hem.
Fout 3: tags als dumpplaats
Te veel tags maakt je site rommelig. Houd het beperkt en consistent.
Fout 4: title tag is te “branding-heavy”
Merknaam | Pagina | Categorie | Jaar is vaak te lang en duwt het onderwerp weg. Zet het onderwerp vooraan, merk optioneel achteraan.
Uitbreidingen: meervoudige varianten, SERP-preview, interne linking hints
Als je generator werkt, zijn dit logische uitbreidingen.
1) Meerdere varianten (A/B)
Je kunt bijvoorbeeld 3 titels genereren:
- Variant A: informatief
- Variant B: met getal
- Variant C: met “tool/template”
In code: laat makeSeoTitle meerdere kandidaten maken en kies op score (lengte, aanwezigheid zoekwoord, etc.).
2) SERP-preview in terminal
Je kunt een “preview” printen:
SEO Metadata Generator (NL) – Titel... | JouwSite
www.jouwsite.nl/seo-metadata-generator-nl...
Genereer automatisch SEO-titels...
Dat helpt redacties om snel te beoordelen of het “klikt”.
3) Interne linking hints
Op basis van tags kun je contentclusters bouwen:
- Als tags
meta descriptionentitle tagvoorkomen, suggereer link naar je technische SEO-pagina. - Dit is vooral nuttig voor grotere sites.
Afronding: wat je nu hebt
Je hebt een werkende Nederlandstalige SEO Metadata Generator als CLI-tool, met:
- titelgeneratie met lengtebeperking,
- descriptiongeneratie met CTA en truncatie op woordgrens,
- slugify + stopwoordenfilter,
- tags op basis van frequentie + bigrammen,
- basischecks om kwaliteit te bewaken,
- echte commands om lokaal te draaien en in pipelines te gebruiken.
Als je wilt, kan ik ook:
- een TypeScript-versie maken,
- een versie die Markdown-bestanden automatisch inleest en metadata terugschrijft,
- of een variant die (optioneel) een LLM gebruikt voor betere copy, met fallback naar heuristieken.