Ensimmäinen blogini

Ongelman kuvaus:
Alustalta puuttuu sisältömarkkinoinnin kanava. Blogi mahdollistaa jokaiselle tenantille SEO-optimoitujen, monikielisten artikkelien julkaisemisen Strapi CMS:ssä ja renderöinnin SvelteKit-sovelluksessa — kasvattaen orgaanista liikennettä, rakentaen asiantuntijuutta ja tukeutuen olemassa olevaan 13-kieliseen i18n-putkeen.
Onnistumiskriteerit
- Strapiin kirjoitetut blogiartikkelit renderöityvät frontendissä vuokralaisen (tenantin) brändäyksen mukaisesti
- Artikkelit käännetään automaattisesti kaikille 13 lokaalille olemassa olevan webhook-putken kautta (ei muutoksia käännösinfraan)
- Blogisivut sijoittuvat hakukoneissa (structured data, sitemap, hreflang, og:article)
- Vuokralaiset, joilla ei ole
blog-feature flagia, eivät näe blogireittejä (404)
Käyttäjätarinat
- Sisällöntuottajana haluan kirjoittaa blogikirjoituksia Strapiin käyttäen rikasta markdownia, kuvia ja video upotuksia, jotta voin julkaista sisältöä koskematta koodiin
- Sivuston vierailijana haluan selata blogikirjoituksia kategorian mukaan suodatettuna, jotta löydän itselleni relevanttia sisältöä
- Sivuston vierailijana haluan sisällysluettelon pitkiin kirjoituksiin, jotta voin liikkua tekstissä nopeasti
- Sivuston vierailijana haluan lukea kirjoituksia omalla kielelläni, automaattisesti englannista käännettynä
- Sivuston vierailijana haluan jakaa kirjoituksia sosiaalisessa mediassa
- Sivuston vierailijana haluan löytää liittyviä kirjoituksia ja saada tietoa kirjoittajasta
- SEO-vastaavana haluan, että jokaisella kirjoituksella on asianmukaiset metatunnisteet, jäsennelty data ja merkinnät sivustokartassa
Toiminnalliset vaatimukset
Pakolliset (MVP)
- Strapi-sisältötyypit:
blog-posts,blog-authors,blog-categories— kaikissa tenant-, locale- ja inherit-kentät - Blogin listaussivu (
/blog) — sivutettu artikkeliruudukko, kategoriansuodatus query-parametrilla - Artikkelin yksityiskohtainen sivu (
/blog/[slug]) — täysi markdown-renderöinti sisällysluettelolla (ToC), kirjoittajakortti, liittyvät artikkelit, sosiaalisen median jako - Kategoriasivu (
/blog/category/[slug]) — suodatettu listaus, joka käyttää samoja listauskomponentteja - Kirjoittajasivu (
/blog/author/[slug]) — kirjoittajan esittely + hänen artikkelinsa - Sisällysluettelo — generoidaan automaattisesti h2/h3-otsikoista, sticky-sivupalkki työpöydällä, mobiilissa kokoonmenevä, aktiivisen otsikon korostus Intersection Observerilla
- YouTube/Vimeo-upotukset — pelkät URL-osoitteet markdownissa muunnetaan automaattisesti responsiivisiksi iframeiksi (youtube-nocookie.com yksityisyyttä varten)
- Lukuaika — lasketaan palvelinpuolella sanamäärästä (Math.ceil(words / 200))
- Sosiaalisen median jakopainikkeet — X, Facebook, LinkedIn, sähköposti, kopioi-linkki (Web Share API, jos saatavilla)
- Liittyvät artikkelit — sama kategoria, nykyinen artikkeli pois rajauksesta, enintään 3, järjestetty publishedAt laskeva
- RSS-syöte (
/blog/rss.xml) — RSS 2.0, locale query-parametrilla - SEO — SeoComponent per artikkeli, og:article-tagit, BlogPosting JSON-LD -skeema, kanoniset URL-osoitteet
- Sivukartta (sitemap) — dynaamiset blogi-/kategoria-/kirjoittaja-URL:t lisätään olemassa olevaan sivukarttaan hreflang-merkinnöillä
- Feature flag —
blog: booleanTenantConfig.features-kentässä, layout-vartija palauttaa 404:n, kun ominaisuus on pois käytöstä - i18n — Paraglide-viestiavaimet kaikille käyttöliittymäteksteille (navigaatio, labelit, sivutus), Strapi-sisältö käännetään automaattisesti olemassa olevan webhookin kautta
- Sivutus — sivupohjainen käyttäen Strapin omaa sivutusta (9 artikkelia per sivu)
- Nostettu artikkeli — boolean-tyyppinen
featured-lippu blog-posts-tyypissä; nostettu artikkeli näytetään hero-korttina/blog-listaussivun yläosassa (isompi kortti, täysi leveys, ruudukon yläpuolella)
Mukava olla (Post-MVP)
- Koko tekstin haku blogiartikkeleista
- Artikkelisarjat / moniosaiset artikkelit
- Lukemisen etenemispalkki
- Kommentit (todennäköisesti kolmannen osapuolen upotuksena)
- Uutiskirjeen tilaus -kutsu toimintaan artikkelien sisällä
- Näyttökertojen seuranta / analytiikka-integraatio
Teknisiä vaatimuksia
Tietomalli (Strapi Content Types)
blog-posts (api::blog-post.blog-post):
| Kenttä | Tyyppi | Lokalisoitu | Huomautukset |
|---|---|---|---|
| title | Teksti (pakollinen) | Kyllä | Sovitettu käännöskonfiguraation mukaan |
| slug | UID (title-kentästä) | Ei | Poissuljettu käännöksestä |
| excerpt | Teksti | Kyllä | Kortteja + meta-kuvausta varten |
| body | Rich Text (Markdown) | Kyllä | Renderöidään marked kautta |
| featuredImage | Media (yksi) | Ei | URL + alternativeText |
| category | Relaatio (manyToOne -> blog-category) | Ei | |
| author | Relaatio (manyToOne -> blog-author) | Ei | |
| featured | Boolean (oletus false) | Ei | Nostoartikkeli, näytetään hero-korttina listauksessa |
| seo | Komponentti (shared.seo) | Kyllä | Käytetään olemassa olevaa SeoComponent-komponenttia |
| tenant | Teksti | Ei | Vakio tenant-suodatin |
| inherit | Boolean (oletus true) | Ei | Automaattisen käännöksen lippu |
blog-authors (api::blog-author.blog-author):
| Kenttä | Tyyppi | Lokalisoitu | Huomautukset |
|---|---|---|---|
| name | Teksti (pakollinen) | Kyllä | |
| slug | UID (name-kentästä) | Ei | |
| bio | Rich Text (Markdown) | Kyllä | |
| avatar | Media (yksi) | Ei | |
| socialLinks | JSON | Ei | { twitter?, linkedin?, website? } |
| tenant | Teksti | Ei | |
| inherit | Boolean (oletus true) | Ei |
blog-categories (api::blog-category.blog-category):
| Kenttä | Tyyppi | Lokalisoitu | Huomautukset |
|---|---|---|---|
| name | Teksti (pakollinen) | Kyllä | |
| slug | UID (name-kentästä) | Ei | |
| description | Teksti | Kyllä | |
| color | Teksti | Ei | Hex-väri UI-tägejä varten |
| tenant | Teksti | Ei | |
| inherit | Boolean (oletus true) | Ei |
TypeScript-rajapinnat
Katso src/lib/services/strapi-types.ts: BlogPost, BlogAuthor, BlogCategory.
Reitit
Kaikki polun src/routes/(marketing)/blog/ alla:
| Reitti | Server Load | Tarkoitus |
|---|---|---|
blog/+layout.server.ts |
Feature guard + kategorioiden lataus | Portinvartija + navigaatiodataa |
blog/+page.server.ts |
Sivutetut postaukset, valinnainen ?category= -suodatin |
Lista |
blog/[slug]/+page.server.ts |
Postaus slugilla + siihen liittyvät postaukset + lukuaika | Yksityiskohtasivu |
blog/category/[slug]/+page.server.ts |
Postaukset suodatettuna kategorian mukaan | Kategorian listaus |
blog/author/[slug]/+page.server.ts |
Kirjoittaja + hänen postauksensa | Kirjoittajasivu |
blog/rss.xml/+server.ts |
RSS 2.0 XML, ?locale= -parametri |
Syöte |
UI-komponentit
Kaikki hakemistossa src/lib/components/blog/:
| Komponentti | Props | Huomautukset |
|---|---|---|
BlogPostCard.svelte |
post: BlogPost |
Kortti, jossa kuva, kategoriamerkki, otsikko, ingressi, kirjoittaja, päivämäärä, lukuaika |
BlogPostContent.svelte |
body: string |
Markdown → HTML marked-kirjaston kautta, videoupotuslaajennus + otsikko-ID:t |
TableOfContents.svelte |
entries: TocEntry[] |
Sticky-sivupalkki, Intersection Observer aktiivisen tilan tunnistukseen, mobiilissa kokoon taittuva |
AuthorCard.svelte |
author: BlogAuthor |
Avatar, nimi, bio, some-linkit |
SocialShare.svelte |
url: string, title: string |
Jakonapit Web Share API -varajärjestelmällä |
RelatedPosts.svelte |
posts: BlogPost[] |
BlogPostCard-ruudukko |
BlogPagination.svelte |
pagination: StrapiPagination, basePath: string |
Edellinen/seuraava + sivunumerot |
Turvallisuus & autentikointi
- Blogi on julkinen (ei vaadi kirjautumista) — markkinointisisältöä
- Strapi API -lukuoikeus nykyisellä
STRAPI_JWT-tunnuksella - Video upotukset käyttävät
youtube-nocookie.com:ää GDPR-vaatimusten täyttämiseksi
Reunatapaukset ja virheenkäsittely
- Julkaisua ei löydy: 404 SvelteKitin kautta
error() - Ei julkaisuja kategoriassa: Tyhjä tila, jossa
blog_no_posts-viesti - Puuttuva esittelykuva: Kortti renderöidään ilman kuvaa
- Käännöstä ei ole saatavilla:
loadContentWithFallback()palaa oletuksena englantiin - RTL-kielet: Ulkoasu noudattaa
dir="rtl"-määritystä (ar, he, ur)
E2E-testisuunnitelma
Testitiedostot sijaitsevat hakemistossa tests/blog/. Seuraa olemassa olevia malleja: parametrisoidut eri näkymäkokojen mukaan, Axe a11y-testeihin, networkidle odottaa Strapi-sisältöä.
Kriittisen polun testit
- Blogilistaus latautuu —
/blogrenderöi ruudukkona artikkelikortteja, joissa on otsikko, ingressi, kategoriatunnus, kirjoittaja, päivämäärä ja lukuaika - Sivutus toimii —
/blog?page=2näyttää toisen sivun tulokset; edellinen/seuraava-painikkeet navigoivat oikein; sivulla 1 ei ole "Previous"-painiketta - Kategoriasuodatin — kategoriasirun klikkaaminen suodattaa artikkelit; URL päivittyy muotoon
?category=<slug>; "All"-siru poistaa suodatuksen - Artikkelisivu renderöityy —
/blog/[slug]näyttää otsikon, esiinnostetun kuvan, muotoillun leipätekstin, kirjoittajakortin, lukuaika-arvion ja julkaisupäivän - Sisällysluettelo — artikkelisivu generoi sisällysluettelon h2/h3-otsikoista; sisällysluettelon kohdan klikkaaminen scrollaa vastaavaan otsikkoon
- Liittyvät artikkelit — artikkelisivu näyttää enintään 3 samaan kategoriaan kuuluvaa muuta artikkelia (pois lukien nykyinen)
- Kategoriakohtainen sivu —
/blog/category/[slug]näyttää kategorian nimen, kuvauksen ja vain kyseisen kategorian artikkelit - Kirjoittajasivu —
/blog/author/[slug]näyttää kirjoittajan esittelyn, avatar-kuvan, sosiaalisen median linkit ja heidän artikkelinsa - Somejako-painikkeet — jakopainikkeet näkyvät artikkelisivulla; "Copy link" kopioi URL-osoitteen ja näyttää vahvistustekstin
- RSS-syöte —
GET /blog/rss.xmlpalauttaa validia XML:ää, jossa onContent-Type: application/rss+xmlja artikkelitietueita - Navigaatio — blogilinkki sivuston valikossa vie osoitteeseen
/blog; murupolku näyttää oikean hierarkian - Nostettu artikkeli —
/blognäyttää nostetun artikkelin isompana hero-korttina varsinaisen ruudukon yläpuolella
Reuna- ja virhetilanteiden testit
- Artikkelia ei löydy —
/blog/nonexistent-slugpalauttaa 404-sivun - Tyhjä kategoria — kategoriassa, jossa ei ole artikkeleita, näytetään
blog_no_posts-tyhjätilaviesti - Puuttuva esiinnostettu kuva — artikkelikortti renderöityy siististi ilman kuvaa (ei rikkinäistä
<img>:ää) - Puuttuva kirjoittaja/kategoria — artikkeli ilman kirjoittajaa tai kategoriaa renderöityy ilman näitä elementtejä (ei kaatumista)
- Feature flag pois päältä — kun
blog: false,/blogpalauttaa 404:n
Poikkileikkaavat testit
- i18n: kielenvaihto — navigoi osoitteeseen
/fr/blog, varmista että käyttöliittymän tekstit (sivun otsikko, "Read more", sivutuslabelit) ovat ranskaksi; artikkelin sisältö fallbackaa englanniksi, jos käännöstä ei ole - i18n: RTL-asettelu — navigoi osoitteisiin
/ar/blogja/ar/blog/[slug], varmista ettädir="rtl"on asetettu ja layout renderöityy oikein (sisällysluettelo oikealla puolella) - SEO: metatiedot — artikkelisivulla on
og:type=article,og:title,og:description,article:published_timeja kanoninen linkki - SEO: JSON-LD — artikkelisivu sisältää
<script type="application/ld+json">-objektin, jossa on@type: BlogPosting, otsikko (headline), kirjoittaja ja datePublished - SEO: sivukartta —
/sitemap.xmlsisältää blogiartikkelien URL-osoitteet hreflang-vastineineen - Saavutettavuus — aja Axe-auditit osoitteissa
/blog,/blog/[slug],/blog/author/[slug]sekä mobiili- että desktop-näkymissä (seuraa olemassa olevaa a11y-testimallia) - Näppäimistönavigointi — sisällysluettelon kohdat ja sivutuspainikkeet ovat fokusoitavissa ja aktivoitavissa näppäimistöllä; fokusindikaattorit ovat näkyvissä
Rajauksen ulkopuolella
- Blogikommentit
- Täystekstihaku
- Uutiskirjeintegraatio julkaisujen sisällä
- Analytiikka / näyttökerrat
- Vuokralaiskohtaiset (tenant-kohtaiset) räätälöidyt julkaisupohjat
Ludo on teknologia-alan kouluttaja ja yrittäjä, jota ohjaa yksinkertainen ajatus: laadukkaan koulutuksen pitäisi olla kaikkien saatavilla.