Salta al contenuto
Intelligenza artificiale

Il mio primo blog

Ludo Fourrage Ludo Fourrage 8 aprile 2026 9 min lettura
Il mio primo blog

Definizione del problema:

La piattaforma non dispone di un canale di content marketing. Un blog permette a ogni tenant di pubblicare articoli multilingue ottimizzati per la SEO, creati in Strapi CMS e renderizzati nell’app SvelteKit — generando traffico organico, costruendo autorevolezza e supportando l’attuale pipeline i18n con 13 locale.

Criteri di successo

  • I post del blog creati in Strapi vengono visualizzati nel frontend con il branding del tenant
  • I post vengono tradotti automaticamente in tutte le 13 lingue tramite la pipeline webhook esistente (nessuna modifica all’infrastruttura di traduzione)
  • Le pagine del blog si posizionano sui motori di ricerca (structured data, sitemap, hreflang, og:article)
  • I tenant senza il feature flag blog non vedono alcuna route del blog (404)

User Stories

  • Come autore di contenuti, voglio scrivere articoli del blog in Strapi con markdown avanzato, immagini e video incorporati così posso pubblicare contenuti senza toccare il codice
  • Come visitatore del sito, voglio sfogliare gli articoli del blog filtrandoli per categoria così posso trovare contenuti rilevanti
  • Come visitatore del sito, voglio un indice dei contenuti negli articoli lunghi così posso navigare rapidamente
  • Come visitatore del sito, voglio leggere gli articoli nella mia lingua, con traduzione automatica dall’inglese
  • Come visitatore del sito, voglio condividere gli articoli sui social media
  • Come visitatore del sito, voglio scoprire articoli correlati e conoscere l’autore
  • Come SEO manager, voglio che ogni articolo abbia meta tag corretti, dati strutturati e voci nella sitemap

Requisiti Funzionali

Must Have (MVP)

  1. Content type Strapi: blog-posts, blog-authors, blog-categories — tutti con campi tenant, locale, inherit
  2. Pagina elenco blog (/blog) — griglia paginata di post, filtro per categoria via query param
  3. Pagina dettaglio post (/blog/[slug]) — rendering completo del markdown con ToC, scheda autore, post correlati, condivisione social
  4. Pagina categoria (/blog/category/[slug]) — elenco filtrato che riutilizza gli stessi componenti di listing
  5. Pagina autore (/blog/author/[slug]) — bio dell’autore + i suoi post
  6. Table of Contents — generata automaticamente dagli heading h2/h3, sidebar fissa su desktop, comprimibile su mobile, evidenziazione dell’heading attivo via Intersection Observer
  7. Embed YouTube/Vimeo — URL “nudi” nel markdown si convertono automaticamente in iframe responsive (youtube-nocookie.com per la privacy)
  8. Tempo di lettura — calcolato server-side dal conteggio parole (Math.ceil(words / 200))
  9. Pulsanti di condivisione social — X, Facebook, LinkedIn, email, copia-link (Web Share API dove disponibile)
  10. Post correlati — stessa categoria, escluso il corrente, limite 3, ordinati per publishedAt desc
  11. Feed RSS (/blog/rss.xml) — RSS 2.0, locale via query param
  12. SEO — SeoComponent per ogni post, tag og:article, schema JSON-LD BlogPosting, URL canoniche
  13. Sitemap — URL dinamiche di post blog/categorie/autori aggiunte alla sitemap esistente con hreflang
  14. Feature flagblog: boolean in TenantConfig.features, layout guard restituisce 404 quando disabilitata
  15. i18n — chiavi di messaggio Paraglide per tutte le stringhe UI (nav, label, paginazione), contenuti Strapi tradotti automaticamente tramite webhook esistente
  16. Paginazione — basata su pagina usando la paginazione nativa di Strapi (9 post per pagina)
  17. Post in evidenza — flag booleano featured su blog-posts; il post in evidenza viene mostrato come hero card in cima alla pagina di listing /blog (card più grande, full-width, sopra la griglia)

Nice to Have (Post-MVP)

  1. Ricerca full-text sui post del blog
  2. Serie di post / articoli in più parti
  3. Barra di avanzamento lettura
  4. Commenti (probabilmente tramite embed di terze parti)
  5. CTA di iscrizione alla newsletter all’interno dei post
  6. Conteggio delle visualizzazioni / integrazione analytics

Requisiti tecnici

Modello dati (Tipi di contenuto Strapi)

blog-posts (api::blog-post.blog-post):

Campo Tipo Localizzato Note
title Text (obbligatorio) Allineato tramite config di traduzione
slug UID (da title) No Escluso dalla traduzione
excerpt Text Per card + meta description
body Rich Text (Markdown) Renderizzato tramite marked
featuredImage Media (single) No URL + alternativeText
category Relation (manyToOne -> blog-category) No
author Relation (manyToOne -> blog-author) No
featured Boolean (default false) No Post in evidenza mostrato come hero card nella lista
seo Component (shared.seo) Riusa il componente SeoComponent esistente
tenant Text No Filtro tenant standard
inherit Boolean (default true) No Flag per auto-traduzione

blog-authors (api::blog-author.blog-author):

Campo Tipo Localizzato Note
name Text (obbligatorio)
slug UID (da name) No
bio Rich Text (Markdown)
avatar Media (single) No
socialLinks JSON No { twitter?, linkedin?, website? }
tenant Text No
inherit Boolean (default true) No

blog-categories (api::blog-category.blog-category):

Campo Tipo Localizzato Note
name Text (obbligatorio)
slug UID (da name) No
description Text
color Text No Colore esadecimale per badge UI
tenant Text No
inherit Boolean (default true) No

Interfacce TypeScript

Vedi src/lib/services/strapi-types.ts per BlogPost, BlogAuthor, BlogCategory.

Route

Tutte sotto src/routes/(marketing)/blog/:

Route Server Load Scopo
blog/+layout.server.ts Feature guard + caricamento categorie Gate + dati di navigazione
blog/+page.server.ts Post paginati, filtro opzionale ?category= Listing
blog/[slug]/+page.server.ts Post per slug + post correlati + calcolo tempo di lettura Dettaglio
blog/category/[slug]/+page.server.ts Post filtrati per categoria Listing per categoria
blog/author/[slug]/+page.server.ts Autore + i suoi post Pagina autore
blog/rss.xml/+server.ts RSS 2.0 XML, parametro ?locale= Feed

Componenti UI

Tutti in src/lib/components/blog/:

Componente Props Note
BlogPostCard.svelte post: BlogPost Card con immagine, badge categoria, titolo, excerpt, autore, data, tempo di lettura
BlogPostContent.svelte body: string Markdown in HTML tramite marked con estensione per embed video + ID sugli heading
TableOfContents.svelte entries: TocEntry[] Sidebar fissa, Intersection Observer per lo stato attivo, comprimibile su mobile
AuthorCard.svelte author: BlogAuthor Avatar, nome, bio, link social
SocialShare.svelte url: string, title: string Pulsanti di condivisione con fallback Web Share API
RelatedPosts.svelte posts: BlogPost[] Griglia di BlogPostCard
BlogPagination.svelte pagination: StrapiPagination, basePath: string Precedente/successiva + numeri di pagina

Sicurezza e autenticazione

  • Il blog è pubblico (non è richiesta autenticazione) — contenuti di marketing
  • Accesso in lettura all’API Strapi tramite il token STRAPI_JWT esistente
  • Gli embed video utilizzano youtube-nocookie.com per la conformità al GDPR

Casi limite e gestione degli errori

  • Post non trovato: 404 tramite SvelteKit error()
  • Nessun post nella categoria: stato vuoto con messaggio blog_no_posts
  • Immagine in evidenza mancante: la card viene renderizzata senza immagine
  • Traduzione non disponibile: loadContentWithFallback() esegue il fallback all'inglese
  • Lingue RTL: il layout rispetta dir="rtl" (ar, he, ur)

Piano di test E2E

I file di test si trovano in tests/blog/. Segui i pattern esistenti: parametrizzazione sui diversi viewport, Axe per l’accessibilità (a11y), networkidle attende il contenuto da Strapi.

Test del percorso critico

  1. Caricamento lista blog/blog rende una griglia di card dei post con titolo, estratto, badge categoria, autore, data e tempo di lettura
  2. Funzionamento della paginazione/blog?page=2 mostra la seconda pagina di risultati; i pulsanti precedente/successivo navigano correttamente; la pagina 1 non ha il pulsante "Precedente"
  3. Filtro per categoria — cliccando su una chip di categoria i post vengono filtrati; l’URL si aggiorna a ?category=<slug>; la chip "Tutti" rimuove il filtro
  4. Rendering dettaglio post/blog/[slug] mostra titolo, immagine in evidenza, corpo formattato, scheda autore, tempo di lettura e data di pubblicazione
  5. Indice (Table of Contents) — la pagina di dettaglio genera l’Indice a partire dai titoli h2/h3; cliccando una voce dell’Indice la pagina scorre fino al titolo corrispondente
  6. Post correlati — il dettaglio post mostra fino a 3 post correlati della stessa categoria (escludendo quello corrente)
  7. Pagina categoria/blog/category/[slug] mostra nome categoria, descrizione e solo i post di quella categoria
  8. Pagina autore/blog/author/[slug] mostra bio dell’autore, avatar, link social e i suoi post
  9. Pulsanti di condivisione social — i pulsanti di condivisione sono visibili sul dettaglio post; "Copia link" copia l’URL e mostra un messaggio di conferma
  10. Feed RSSGET /blog/rss.xml restituisce XML valido con Content-Type: application/rss+xml e contiene le entry dei post
  11. Navigazione — il link al blog nel menu del sito porta a /blog; i breadcrumb mostrano la gerarchia corretta
  12. Post in evidenza/blog visualizza il post in evidenza come card hero più grande sopra la griglia normale

Test di edge case ed errori

  1. Post non trovato/blog/nonexistent-slug restituisce una pagina 404
  2. Categoria vuota — una categoria senza post mostra il messaggio di stato vuoto blog_no_posts
  3. Immagine in evidenza mancante — la card del post viene renderizzata correttamente anche senza immagine (nessuna <img> rotta)
  4. Autore/categoria mancanti — un post senza autore o categoria viene renderizzato senza quegli elementi (nessun crash)
  5. Feature flag disabilitato — quando blog: false, /blog restituisce 404

Test trasversali

  1. i18n: cambio lingua — navigare a /fr/blog, verificare che le stringhe UI (titolo pagina, "Leggi di più", etichette della paginazione) siano in francese; il contenuto del post ricade sull’inglese se non tradotto
  2. i18n: layout RTL — navigare a /ar/blog e /ar/blog/[slug], verificare che dir="rtl" sia impostato e che il layout venga renderizzato correttamente (Indice sul lato corretto)
  3. SEO: meta tag — la pagina di dettaglio del post ha og:type=article, og:title, og:description, article:published_time e il link canonico
  4. SEO: JSON-LD — la pagina di dettaglio del post contiene un <script type="application/ld+json"> con @type: BlogPosting, headline, autore, datePublished
  5. SEO: sitemap/sitemap.xml include gli URL dei post del blog con le varianti hreflang
  6. Accessibilità — eseguire audit Axe su /blog, /blog/[slug], /blog/author/[slug] sia su viewport mobile sia desktop (seguendo il pattern di test a11y esistente)
  7. Navigazione da tastiera — le voci dell’Indice e i pulsanti di paginazione sono focalizzabili e attivabili via tastiera; gli indicatori di focus sono visibili

Fuori ambito

  • Commenti ai post del blog
  • Ricerca full-text
  • Integrazione newsletter all'interno dei post
  • Analitiche / conteggio visualizzazioni
  • Modelli di post personalizzati per tenant
Ludo Fourrage
Ludo Fourrage

Ludo è un formatore e imprenditore nel settore tecnologico, guidato da una semplice convinzione: un'istruzione di qualità dovrebbe essere accessibile a tutti.