Ir para o conteúdo
IA

Meu primeiro blog

Ludo Fourrage Ludo Fourrage 8 de abril de 2026 10 min leitura
Meu primeiro blog

Enunciado do Problema:

A plataforma não dispõe de um canal de marketing de conteúdo. Um blog permite que cada tenant publique artigos multilíngues otimizados para SEO, redigidos no Strapi CMS e renderizados na aplicação SvelteKit — gerando tráfego orgânico, estabelecendo autoridade e suportando o pipeline de i18n já existente com 13 locales.

Critérios de sucesso

  • Posts de blog criados no Strapi são renderizados no frontend dentro da identidade visual do tenant
  • Posts são traduzidos automaticamente para os 13 idiomas via o pipeline de webhook existente (sem mudanças na infraestrutura de tradução)
  • Páginas de blog ranqueiam em motores de busca (dados estruturados, sitemap, hreflang, og:article)
  • Tenants sem o feature flag blog não exibem rotas de blog (404)

User Stories

  • Como autor de conteúdo, quero escrever posts de blog no Strapi com markdown avançado, imagens e vídeos incorporados para poder publicar conteúdo sem tocar em código
  • Como visitante do site, quero navegar por posts filtrados por categoria para encontrar conteúdo relevante
  • Como visitante do site, quero um índice em posts longos para conseguir navegar rapidamente
  • Como visitante do site, quero ler os posts no meu idioma, com tradução automática a partir do inglês
  • Como visitante do site, quero partilhar os posts nas redes sociais
  • Como visitante do site, quero descobrir posts relacionados e saber mais sobre o autor
  • Como gestor de SEO, quero que cada post tenha meta tags corretas, dados estruturados e entradas no sitemap

Requisitos Funcionais

Must Have (MVP)

  1. Tipos de conteúdo Strapi: blog-posts, blog-authors, blog-categories — todos com campos tenant, locale, inherit
  2. Página de listagem do blog (/blog) — grelha paginada de posts, filtro por categoria via query param
  3. Página de detalhe do post (/blog/[slug]) — renderização completa de markdown com ToC, cartão de autor, posts relacionados, partilha social
  4. Página de categoria (/blog/category/[slug]) — listagem filtrada reutilizando os mesmos componentes de listagem
  5. Página de autor (/blog/author/[slug]) — biografia do autor + os seus posts
  6. Tabela de Conteúdos — gerada automaticamente a partir de headings h2/h3, sidebar fixa no desktop, colapsável no mobile, destaque do heading ativo via Intersection Observer
  7. Embeds de YouTube/Vimeo — URLs simples em markdown convertem-se automaticamente em iframes responsivos (youtube-nocookie.com por motivos de privacidade)
  8. Tempo de leitura — calculado no servidor a partir da contagem de palavras (Math.ceil(words / 200))
  9. Botões de partilha social — X, Facebook, LinkedIn, email, copiar link (Web Share API quando disponível)
  10. Posts relacionados — mesma categoria, excluir o atual, limite de 3, ordenados por publishedAt desc
  11. Feed RSS (/blog/rss.xml) — RSS 2.0, locale via query param
  12. SEO — SeoComponent por post, tags og:article, schema JSON-LD BlogPosting, URLs canónicas
  13. Sitemap — URLs dinâmicas de posts de blog / categoria / autor adicionadas ao sitemap existente com hreflang
  14. Feature flagblog: boolean em TenantConfig.features, guarda de layout devolve 404 quando desativado
  15. i18n — chaves de mensagem Paraglide para todas as strings de UI (nav, labels, paginação), conteúdo Strapi traduzido automaticamente via webhook existente
  16. Paginação — baseada em páginas usando a paginação nativa do Strapi (9 posts por página)
  17. Post em destaque — flag booleana featured em blog-posts; post em destaque exibido como um cartão hero no topo da página de listagem /blog (cartão maior, largura total, acima da grelha)

Nice to Have (Pós-MVP)

  1. Pesquisa de texto integral em todos os posts do blog
  2. Séries de posts / artigos em múltiplas partes
  3. Barra de progresso de leitura
  4. Comentários (provavelmente via embed de terceiros)
  5. CTA de inscrição na newsletter dentro dos posts
  6. Contador de visualizações / integração de analytics

Requisitos Técnicos

Modelo de Dados (Tipos de Conteúdo Strapi)

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

Campo Tipo Localizado Notas
title Texto (obrigatório) Sim Correspondente na config de tradução
slug UID (a partir de title) Não Excluído da tradução
excerpt Texto Sim Para cards + meta description
body Rich Text (Markdown) Sim Renderizado via marked
featuredImage Media (single) Não URL + alternativeText
category Relation (manyToOne -> blog-category) Não
author Relation (manyToOne -> blog-author) Não
featured Boolean (padrão false) Não Post em destaque exibido como card hero na listagem
seo Component (shared.seo) Sim Reutiliza o SeoComponent existente
tenant Texto Não Filtro padrão de tenant
inherit Boolean (padrão true) Não Flag de tradução automática

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

Campo Tipo Localizado Notas
name Texto (obrigatório) Sim
slug UID (a partir de name) Não
bio Rich Text (Markdown) Sim
avatar Media (single) Não
socialLinks JSON Não { twitter?, linkedin?, website? }
tenant Texto Não
inherit Boolean (padrão true) Não

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

Campo Tipo Localizado Notas
name Texto (obrigatório) Sim
slug UID (a partir de name) Não
description Texto Sim
color Texto Não Cor em hex para badges de UI
tenant Texto Não
inherit Boolean (padrão true) Não

Interfaces TypeScript

Ver src/lib/services/strapi-types.ts para BlogPost, BlogAuthor, BlogCategory.

Rotas

Todas em src/routes/(marketing)/blog/:

Rota Server Load Finalidade
blog/+layout.server.ts Feature guard + carregamento de categorias Gate + dados de navegação
blog/+page.server.ts Posts paginados, filtro opcional ?category= Listagem
blog/[slug]/+page.server.ts Post por slug + posts relacionados + cálculo de tempo de leitura Detalhe
blog/category/[slug]/+page.server.ts Posts filtrados por categoria Listagem por categoria
blog/author/[slug]/+page.server.ts Autor + seus posts Página de autor
blog/rss.xml/+server.ts RSS 2.0 XML, parâmetro ?locale= Feed

Componentes de UI

Todos em src/lib/components/blog/:

Componente Props Notas
BlogPostCard.svelte post: BlogPost Card com imagem, badge de categoria, título, excerto, autor, data, tempo de leitura
BlogPostContent.svelte body: string Markdown para HTML via marked com extensão de incorporação de vídeo + IDs em headings
TableOfContents.svelte entries: TocEntry[] Sidebar fixa, Intersection Observer para estado ativo, colapsável em mobile
AuthorCard.svelte author: BlogAuthor Avatar, nome, bio, links sociais
SocialShare.svelte url: string, title: string Botões de partilha com fallback para Web Share API
RelatedPosts.svelte posts: BlogPost[] Grid de BlogPostCard
BlogPagination.svelte pagination: StrapiPagination, basePath: string Anterior/próximo + números de página

Segurança & Autenticação

  • Blog é público (sem autenticação necessária) — conteúdo de marketing
  • Acesso de leitura à API Strapi através do token STRAPI_JWT existente
  • Embeds de vídeo usam youtube-nocookie.com para conformidade com o RGPD

Casos de Borda & Tratamento de Erros

  • Publicação não encontrada: 404 via SvelteKit error()
  • Nenhuma publicação na categoria: Estado vazio com mensagem blog_no_posts
  • Imagem de destaque ausente: Cartão é exibido sem imagem
  • Tradução não disponível: loadContentWithFallback() recua para o inglês
  • Idiomas RTL: Layout respeita dir="rtl" (ar, he, ur)

Plano de Testes E2E

Os arquivos de teste ficam em tests/blog/. Siga os padrões existentes: parametrização entre diferentes viewports, Axe para acessibilidade (a11y), networkidle aguarda o conteúdo do Strapi.

Testes de Caminho Crítico

  1. Listagem do blog carrega/blog renderiza uma grade de cards de posts com título, excerto, selo de categoria, autor, data e tempo de leitura
  2. Paginação funciona/blog?page=2 mostra a segunda página de resultados; os botões anterior/próximo navegam corretamente; a página 1 não tem botão “Anterior”
  3. Filtro por categoria — clicar em um chip de categoria filtra os posts; a URL é atualizada para ?category=<slug>; o chip “Todos” limpa o filtro
  4. Detalhe do post renderiza/blog/[slug] mostra título, imagem em destaque, corpo formatado, card do autor, tempo de leitura e data de publicação
  5. Tabela de Conteúdos (ToC) — a página de detalhe do post gera uma ToC a partir dos headings h2/h3; clicar em uma entrada da ToC rola até o heading correspondente
  6. Posts relacionados — a página de detalhe do post mostra até 3 posts relacionados da mesma categoria (excluindo o atual)
  7. Página de categoria/blog/category/[slug] mostra o nome da categoria, descrição e apenas posts daquela categoria
  8. Página de autor/blog/author/[slug] mostra a bio do autor, avatar, links sociais e seus posts
  9. Botões de compartilhamento social — os botões de compartilhamento são visíveis no detalhe do post; “Copiar link” copia a URL e exibe um texto de confirmação
  10. Feed RSSGET /blog/rss.xml retorna XML válido com Content-Type: application/rss+xml e contém entradas de posts
  11. Navegação — o link de blog no menu do site navega para /blog; breadcrumbs mostram a hierarquia correta
  12. Post em destaque/blog exibe o post em destaque como um card hero maior acima da grade normal

Testes de Casos Limite & Erros

  1. Post não encontrado/blog/nonexistent-slug retorna uma página 404
  2. Categoria vazia — uma categoria sem posts exibe a mensagem de estado vazio blog_no_posts
  3. Imagem em destaque ausente — o card do post é renderizado corretamente sem imagem (nenhuma <img> quebrada)
  4. Autor/categoria ausentes — post sem autor ou categoria é renderizado sem esses elementos (sem crash)
  5. Feature flag desativada — quando blog: false, /blog retorna 404

Testes Transversais

  1. i18n: troca de locale — navegar para /fr/blog, verificar que as strings de UI (título da página, “Ler mais”, labels de paginação) estão em francês; o conteúdo do post permanece em inglês se não houver tradução
  2. i18n: layout RTL — navegar para /ar/blog e /ar/blog/[slug], verificar que dir="rtl" está definido e o layout é renderizado corretamente (ToC no lado correto)
  3. SEO: meta tags — a página de detalhe do post tem og:type=article, og:title, og:description, article:published_time e link canônico
  4. SEO: JSON-LD — a página de detalhe do post contém um <script type="application/ld+json"> com @type: BlogPosting, headline, autor, datePublished
  5. SEO: sitemap/sitemap.xml inclui URLs de posts do blog com alternâncias hreflang
  6. Acessibilidade — rodar auditorias Axe em /blog, /blog/[slug], /blog/author/[slug] tanto em viewport mobile quanto desktop (seguindo o padrão existente de testes de a11y)
  7. Navegação por teclado — entradas da ToC e botões de paginação são focalizáveis e acionáveis via teclado; os indicadores de foco são visíveis

Fora de Escopo

  • Comentários em posts de blog
  • Busca em texto integral
  • Integração de newsletter dentro dos posts
  • Análises/contagem de visualizações
  • Modelos de post personalizados por tenant
Ludo Fourrage
Ludo Fourrage

Ludo é um educador e empreendedor na área de tecnologia, movido por uma crença simples: educação de qualidade deve ser acessível a todos.