Min første blogg

Problem Statement:
Plattformen mangler en innholdsmarkedsføringskanal. En blogg gjør det mulig for hver tenant å publisere SEO-optimaliserte, flerspråklige artikler skrevet i Strapi CMS og gjengitt i SvelteKit-appen – noe som øker organisk trafikk, bygger autoritet og støtter den eksisterende i18n-pipelinen med 13 lokaliteter.
Suksesskriterier
- Blogginnlegg skrevet i Strapi vises på frontend med leietakerens egen branding
- Innlegg autotranslateres til alle 13 språkversjoner via eksisterende webhook-pipeline (ingen endringer i oversettelsesinfrastrukturen)
- Bloggsider rangerer i søkemotorer (structured data, sitemap, hreflang, og:article)
- Leietakere uten
blogfeature-flagget får ingen bloggruter (404)
User Stories
- Som innholdsforfatter vil jeg skrive blogginnlegg i Strapi med rik markdown, bilder og video-innbygginger slik at jeg kan publisere innhold uten å røre kode
- Som en nettstedsbesøkende vil jeg bla i blogginnlegg filtrert etter kategori slik at jeg kan finne relevant innhold
- Som en nettstedsbesøkende vil jeg ha en innholdsfortegnelse på lange innlegg slik at jeg kan navigere raskt
- Som en nettstedsbesøkende vil jeg lese innlegg på mitt språk, med automatisk oversettelse fra engelsk
- Som en nettstedsbesøkende vil jeg dele innlegg i sosiale medier
- Som en nettstedsbesøkende vil jeg oppdage relaterte innlegg og bli kjent med forfatteren
- Som en SEO-ansvarlig vil jeg at hvert innlegg skal ha riktige metatagger, strukturert data og oppføringer i sitemapet
Funksjonelle krav
Må ha (MVP)
- Strapi-innholdstyper:
blog-posts,blog-authors,blog-categories— alle med tenant, locale, inherit-felter - Bloggoversiktsside (
/blog) — paginert rutenett med innlegg, kategorifilter via query-param - Detaljside for innlegg (
/blog/[slug]) — full markdown-rendering med innholdsfortegnelse, forfatterkort, relaterte innlegg, deling i sosiale medier - Kategoriside (
/blog/category/[slug]) — filtrert oversikt som gjenbruker de samme listekomponentene - Forfatterside (
/blog/author/[slug]) — forfatterbio + deres innlegg - Innholdsfortegnelse — auto-generert fra h2/h3-overskrifter, sticky sidebar på desktop, kollapsbar på mobil, aktiv overskrift markeres via Intersection Observer
- YouTube/Vimeo-innbygging — rene URL-er i markdown auto-konverteres til responsive iframes (youtube-nocookie.com for personvern)
- Lesetid — beregnes server-side ut fra ordantall (Math.ceil(words / 200))
- Delingsknapper for sosiale medier — X, Facebook, LinkedIn, e-post, kopier-lenke (Web Share API der det er tilgjengelig)
- Relaterte innlegg — samme kategori, ekskluder nåværende, maks 3, sortert på publishedAt desc
- RSS-feed (
/blog/rss.xml) — RSS 2.0, locale via query-param - SEO — SeoComponent per innlegg, og:article-tags, BlogPosting JSON-LD-skjema, kanoniske URL-er
- Sitemap — dynamiske URL-er for blogginnlegg/kategori/forfatter legges til eksisterende sitemap med hreflang
- Feature flag —
blog: booleani TenantConfig.features, layout-guard returnerer 404 når deaktivert - i18n — Paraglide-meldingsnøkler for alle UI-strenger (navigasjon, etiketter, paginering), Strapi-innhold auto-oversettes via eksisterende webhook
- Paginering — sidebasert ved bruk av Strapis innebygde paginering (9 innlegg per side)
- Fremhevet innlegg — boolsk
featured-flagg på blog-posts; fremhevet innlegg vises som et hero-kort øverst på/blog-oversiktssiden (større kort, full bredde, over rutenettet)
Kjekt å ha (Post-MVP)
- Fritekstsøk på tvers av blogginnlegg
- Innleggsserier / artikler i flere deler
- Lesefremdriftsindikator
- Kommentarer (trolig via tredjeparts-embed)
- CTA for nyhetsbrevpåmelding inne i innlegg
- Visningsteller / analyseintegrasjon
Tekniske krav
Datamodell (Strapi Content Types)
blog-posts (api::blog-post.blog-post):
| Felt | Type | Lokaliseres | Notater |
|---|---|---|---|
| title | Tekst (obligatorisk) | Ja | Matches av oversettelseskonfigurasjon |
| slug | UID (fra title) | Nei | Ekskludert fra oversettelse |
| excerpt | Tekst | Ja | For kortvisning + meta-beskrivelse |
| body | Rich Text (Markdown) | Ja | Renderes via marked |
| featuredImage | Media (enkelt) | Nei | URL + alternativeText |
| category | Relasjon (manyToOne -> blog-category) | Nei | |
| author | Relasjon (manyToOne -> blog-author) | Nei | |
| featured | Boolean (standard false) | Nei | Fremhevet innlegg vises som hero-kort i listing |
| seo | Komponent (shared.seo) | Ja | Gjenbruk eksisterende SeoComponent |
| tenant | Tekst | Nei | Standard tenant-filter |
| inherit | Boolean (standard true) | Nei | Auto-oversettelsesflagg |
blog-authors (api::blog-author.blog-author):
| Felt | Type | Lokaliseres | Notater |
|---|---|---|---|
| name | Tekst (obligatorisk) | Ja | |
| slug | UID (fra name) | Nei | |
| bio | Rich Text (Markdown) | Ja | |
| avatar | Media (enkelt) | Nei | |
| socialLinks | JSON | Nei | { twitter?, linkedin?, website? } |
| tenant | Tekst | Nei | |
| inherit | Boolean (standard true) | Nei |
blog-categories (api::blog-category.blog-category):
| Felt | Type | Lokaliseres | Notater |
|---|---|---|---|
| name | Tekst (obligatorisk) | Ja | |
| slug | UID (fra name) | Nei | |
| description | Tekst | Ja | |
| color | Tekst | Nei | Hex-farge for UI-badges |
| tenant | Tekst | Nei | |
| inherit | Boolean (standard true) | Nei |
TypeScript-grensesnitt
Se src/lib/services/strapi-types.ts for BlogPost, BlogAuthor, BlogCategory.
Ruter
Alle under src/routes/(marketing)/blog/:
| Rute | Server-load | Formål |
|---|---|---|
blog/+layout.server.ts |
Feature guard + laste kategorier | Gate + navigasjonsdata |
blog/+page.server.ts |
Paginert liste med innlegg, valgfritt ?category=-filter |
Listing |
blog/[slug]/+page.server.ts |
Innlegg etter slug + relaterte innlegg + beregning av lesetid | Detalj |
blog/category/[slug]/+page.server.ts |
Innlegg filtrert på kategori | Kategorilisting |
blog/author/[slug]/+page.server.ts |
Forfatter + deres innlegg | Forfatterside |
blog/rss.xml/+server.ts |
RSS 2.0 XML, ?locale=-parameter |
Feed |
UI-komponenter
Alle i src/lib/components/blog/:
| Komponent | Props | Notater |
|---|---|---|
BlogPostCard.svelte |
post: BlogPost |
Kort med bilde, kategorimerke, tittel, utdrag, forfatter, dato, lesetid |
BlogPostContent.svelte |
body: string |
Markdown til HTML via marked med video-embed-utvidelse + heading-ID-er |
TableOfContents.svelte |
entries: TocEntry[] |
Sticky sidebar, Intersection Observer for aktiv tilstand, sammenleggbar på mobil |
AuthorCard.svelte |
author: BlogAuthor |
Avatar, navn, bio, sosiale lenker |
SocialShare.svelte |
url: string, title: string |
Deleknapper med fallback til Web Share API |
RelatedPosts.svelte |
posts: BlogPost[] |
Grid med BlogPostCard |
BlogPagination.svelte |
pagination: StrapiPagination, basePath: string |
Forrige/neste + sidenumre |
Sikkerhet og autentisering
- Blogg er offentlig (ingen autentisering kreves) — markedsføringsinnhold
- Strapi API-lesetilgang via eksisterende
STRAPI_JWT-token - Video-innbygging bruker
youtube-nocookie.comfor GDPR-kompatibilitet
Randtilfeller og feilhåndtering
- Innlegg ikke funnet: 404 via SvelteKit
error() - Ingen innlegg i kategori: Tomtilstand med
blog_no_posts-melding - Mangler fremhevet bilde: Kortet rendres uten bilde
- Oversettelse ikke tilgjengelig:
loadContentWithFallback()faller tilbake til engelsk - RTL-språk: Layout respekterer
dir="rtl"(ar, he, ur)
E2E testplan
Testfilene ligger i tests/blog/. Følg eksisterende mønstre: parameterisert på tvers av visningsflater (viewports), Axe for universell utforming (a11y), networkidle venter på Strapi-innhold.
Kritiske flyttester
- Bloggoversikt lastes —
/blogviser et rutenett med postkort med tittel, utdrag, kategorimerke, forfatter, dato og lesetid - Paginering fungerer —
/blog?page=2viser side 2 av resultatene; forrige/neste-knapper navigerer riktig; side 1 har ingen «Forrige»-knapp - Kategorifilter — å klikke på en kategoribrikke filtrerer innlegg; URL oppdateres til
?category=<slug>; «Alle»-brikken fjerner filteret - Innleggsdetaljer rendres —
/blog/[slug]viser tittel, fremhevet bilde, formatert brødtekst, forfatterkort, lesetid og publiseringsdato - Innholdsfortegnelse — detaljsiden for innlegg genererer innholdsfortegnelse fra h2/h3-overskrifter; klikk på en ToC-oppføring scroller til riktig overskrift
- Relaterte innlegg — detaljsiden viser opptil 3 relaterte innlegg fra samme kategori (uten å inkludere gjeldende innlegg)
- Kategori-side —
/blog/category/[slug]viser kategoriens navn, beskrivelse og kun innlegg fra den kategorien - Forfatterside —
/blog/author/[slug]viser forfatterbio, avatar, sosiale lenker og deres innlegg - Delingsknapper for sosiale medier — delingsknapper er synlige på detaljsiden; «Kopier lenke» kopierer URL-en og viser en bekreftelsestekst
- RSS-feed —
GET /blog/rss.xmlreturnerer gyldig XML medContent-Type: application/rss+xmlog inneholder innleggsoppføringer - Navigasjon — blogglenken i hovedmenyen navigerer til
/blog; brødsmulestien viser korrekt hierarki - Fremhevet innlegg —
/blogviser det fremhevede innlegget som et større hero-kort over det vanlige rutenettet
Randtilfeller og feilhåndtering
- Innlegg ikke funnet —
/blog/nonexistent-slugreturnerer en 404-side - Tom kategori — en kategori uten innlegg viser tomtilstands-meldingen
blog_no_posts - Manglende fremhevet bilde — postkort rendres på en god måte uten bilde (ingen ødelagt
<img>) - Manglende forfatter/kategori — innlegg uten forfatter eller kategori rendres uten disse elementene (ingen krasj)
- Feature flag deaktivert — når
blog: false, returnerer/blog404
Tverrgående tester
- i18n: bytte av språkversjon — naviger til
/fr/blog, verifiser at UI-strenger (sidetittel, «Les mer», pagineringsetiketter) er på fransk; innholdsteksten faller tilbake til engelsk hvis den ikke er oversatt - i18n: RTL-layout — naviger til
/ar/blogog/ar/blog/[slug], verifiser atdir="rtl"er satt og at layouten rendres korrekt (ToC på riktig side) - SEO: metatagger — detaljsiden for innlegg har
og:type=article,og:title,og:description,article:published_timeog kanonisk lenke - SEO: JSON-LD — detaljsiden for innlegg inneholder en
<script type="application/ld+json">med@type: BlogPosting, overskrift, forfatter, datePublished - SEO: sitemap —
/sitemap.xmlinkluderer URL-er til blogginnlegg med hreflang-alternativer - Tilgjengelighet — kjør Axe-audits på
/blog,/blog/[slug],/blog/author/[slug]både på mobil- og desktop-visningsflater (følger eksisterende a11y-testmønster) - Tastaturnavigasjon — ToC-oppføringer og pagineringsknapper kan fokuseres og aktiveres via tastatur; fokusmarkeringer er synlige
Utenfor omfang
- Kommentarer på blogginnlegg
- Fulltekstsøk
- Integrasjon av nyhetsbrev i innlegg
- Analyse/visningsstatistikk
- Egendefinerte innleggsmaler per leietaker
Ludo er lærer og gründer innen teknologi, drevet av en enkel tro: utdanning av høy kvalitet skal være tilgjengelig for alle.