Dynamiske CMS-links: hvorfor dit 'Link'-felt er en tidsbombe
De fleste CMS-implementeringer lader forfattere indsætte rå URL'er i et tekstfelt. Den dag, nogen omdøber en slug, 404'er halvdelen af din navigation i stilhed. Her er modellen, der gør det umuligt.
Tirsdag morgen-hændelsen
En tirsdag morgen omdøber en marketing-medarbejder en kampagneside i CMS'et. De ændrer /kampagner/forar-2024 til /kampagner/forar-launch. Tre nav-punkter, to forsidekort og en betalt ads-landingsside peger nu på en 404. Ingen opdager det i elleve dage. Betalt trafik blev ved med at ankomme hele tiden.
Det er ikke en forfatterfejl. Det er en modelfejl. CMS'et gav dem et tekstfelt og en fodgun.
Standarden: et tekstfelt
I den gennemsnitlige CMS-opsætning er "link til en anden side" implementeret som et string-felt. Forfatteren forventes at huske URL'en, taste eller indsætte den korrekt, og opdatere hver reference i hånden, hvis den nogensinde ændrer sig.
Der er ingen validering af, at URL'en eksisterer. Ingen advarsel, hvis målet er upublished, slettet eller flyttet. Ingen måde for CMS'et at fortælle dig seks måneder senere: "disse 47 steder linker til en side, der ikke længere eksisterer." Bureauet, der byggede det, leverede til tiden. Omkostningen blev udskudt til forfatteren og til din trafik.
Hvorfor det bryder sammen i skala
Det kræver ikke et redesign for at bryde sammen. De daglige operationer i et content-team bryder det:
- Slug-ændringer. SEO omdøber en side af keyword-grunde. Hvert tekstfelt-link, der pegede på den gamle slug, brækker.
- Locale-varianter. Den engelske forfatter taster
/about. Den danske version er/da/om-os. Halvdelen af de lokaliserede sider linker ud af locale. - Forælder/barn-flytninger. En side flyttes under en ny forælder. Dens fulde sti ændrer sig. Hvert link til den er nu forkert.
- Sletninger og unpublish. En side tages ned. Hvert tekst-link til den peger på en 404 for evigt.
- Domæne- eller scheme-ændringer. Trailing slashes, http vs. https, www vs. apex. Vælg en URL-detalje og find den forfatter, der tastede den anderledes.
Et tekstfelt til "link til en side" er en database uden foreign keys. Det vil svigte dig i det øjeblik, dit content-team udfører normalt arbejde.
Den rigtige model: links som referencer
Stop med at gemme URL'er. Gem en reference til den entry, forfatteren vil linke til, og beregn URL'en fra entryen. Linkfeltet på hver indholdstype bliver:
- En reference til en
Page-entry (eller hvilke entry-typer der nu er linkbare). - En valgfri override-label, så forfattere kan sige "link til denne side, men kald den 'Læs historien'."
- En valgfri anchor til in-page-jumps.
Forfatteren behøver ikke længere kende URL'er. De søger på sidetitel, vælger entryen, og systemet garanterer, at linket vil resolve, så længe siden eksisterer.
Forælder/barn-relationer som kilden til sandheden
Den anden halvdel af løsningen er at modellere navigationshierarkiet i selve CMS'et. I de fleste opsætninger lever URL'en for en side tre steder: et slug-felt, en redaktørs hukommelse, og en hånd-vedligeholdt navigationsmenu. Tre kilder, ingen enighed.
Modellen, der holder:
- Ét felt på siden: slug-segmentet for kun den side (fx
forar-launch, ikke den fulde sti). - Et self-reference-felt: forælder, der peger på siden ovenover i træet.
- En beregnet fuld sti, afledt ved at gennemløbe fra siden til roden og samle slug-segmenterne.
- Navigationsmenuen genereres fra dette træ, vedligeholdes ikke i hånden.
Når en side flyttes under en ny forælder, opdateres hver URL under den. Hvert reference-link til den fortsætter med at resolve, fordi referencer gemmer entry-ID'er, ikke stier.
Sådan løste jeg det i Contentful
Mønsteret nedenfor er den præcise model, jeg nu bruger på hvert Contentful-projekt. Den er lille. Den er forskellen mellem et CMS, der ældes godt, og et, der forfalder til en kirkegård af brudte links.
Page-indholdstypen
En enkelt Page-type dækker hver URL på sitet, med en self-reference til hierarki.
title: Symbol // krævet, indekseret
slug: Symbol // krævet, kun segment ("forar-launch")
parent: Link<Entry<Page>> // valgfri, self-reference
locale: Symbol // krævet (en, da, ...)
sections: Array<Link<Entry<Section>>>
seo: Link<Entry<Seo>>
// Validering:
// - slug er unik blandt sider med samme forælder + locale
// - forælderen skal være af typen Page og samme locale
// - cycle-check: en side kan ikke være sin egen forfaderLinkfelt brugt alle andre steder
Hver anden indholdstype, der har brug for et link — CTA'er, nav-punkter, kort, rich-text-inline-links — bruger samme PageLink-form. Aldrig en URL-streng.
page: Link<Entry<Page>> // krævet
label: Symbol // valgfri override
anchor: Symbol // valgfri, fx "pricing"
rel: Symbol // valgfri, fx "nofollow"
// Eksterne links er en separat type (ExternalLink),
// så modellen selv forhindrer, at man taster en URL i et felt,
// der skulle være internt.Path resolver
En lille serverfunktion forvandler enhver Page til dens fulde URL ved at gennemløbe forælder-kæden. Resultatet caches pr. side og invalideres af Contentful-webhooks, når en sides slug eller forælder ændres.
type PageRef = { sys: { id: string }; fields: { slug: string; parent?: PageRef; locale: string } }
export function resolvePath(page: PageRef): string {
const segments: string[] = []
let current: PageRef | undefined = page
while (current) {
segments.unshift(current.fields.slug)
current = current.fields.parent
}
const localePrefix = page.fields.locale === "en" ? "" : `/${page.fields.locale}`
return `${localePrefix}/${segments.join("/")}`.replace(/\/+/g, "/")
}Hvad redaktøren ser
Når en forfatter indsætter et link, søger de på siden ved titel i Contentfuls reference-picker. CMS'et viser den resolvede URL som et read-only preview. Hvis målsiden er upublished eller slettet, viser Contentful en broken-reference-warning direkte i editoren — før ændringen går live, ikke elleve dage senere.
At omdøbe en slug, flytte en side under en ny forælder eller unpublish'e en side opdaterer nu hvert link automatisk, fordi intet link nogensinde gemte URL'en til at starte med.
Migrationssti fra tekstfelt-links
Du behøver ikke bygge om for at adoptere det. Migrationen er mekanisk:
- Tilføj den nye
PageLink-reference-type ved siden af det eksisterende tekst-URL-felt. Slet ikke det gamle endnu. - Kør et engangsscript, der gennemløber hver entry, parser URL-strengen, finder den matchende
Pageved sti og skriver referencen. Log de umatchede URL'er. - Triagér den umatchede liste. De fleste vil være eksterne links (flyt dem til
ExternalLink) eller links til sider, der ikke længere eksisterer (de var allerede brudte — nu ved du det). - Stil en redirect-tabel op til de 404'er, du fandt. Den er også CMS-styret: en
Redirect-type medfrom-streng ogtosom enPageLink. - Skjul det gamle tekst-URL-felt i editoren. Fjern det efter én publiceringscyklus.
Det her er ikke Contentful-specifikt
Vil du have denne slags vurdering på dit projekt?
Jeg læser hver e-mail inden for én arbejdsdag. Tag et projekt, et tilbud eller et system, du sidder fast i.
15 ting hvert Contentful enterprise-projekt får galt i de første 6 uger
De 15 produktions-huller hvert enterprise Contentful + Next.js-build rammer i de første seks uger — og hvordan du lukker hver enkelt uden at brænde et sprint. En pre-kickoff-checkliste til tech-leads på en Contentful enterprise starter.
REST + GraphQL-hybrider til multi-locale CMS-drevne sites
Hvorfor hverken kun REST eller kun GraphQL er det rigtige valg til et enterprise multi-locale CMS-site, og hvordan du splitter efter ansvar i stedet. Inkluderer cirkulære referencer på fulde REST-payloads, bundle-omkostningen ved GraphQL på klienten, beslutningsmatricen pr. kaldsted, den samlede fetcher, granulære cache-tags, blok-som-fragment-mønsteret, locale-fallback i ét round-trip, Live Preview der overlever, Server Actions til CMA-skrivninger, Algolia Sync API-undtagelsen og migrationsrækkefølgen.
CMS-styret analyse: stop med at betale udviklere for hver tracking-ændring
Sådan strukturerer du dit CMS, så marketing kan tilføje tracking-hændelser uden udviklerindblanding. Spar sekscifrede DKK-beløb over sitets levetid.