Blog Leonardo Leão
Tutoriais

Como funciona o blog template Astro do Sinkra Hub: tour técnico completo

O blog template do Sinkra Hub é Astro 6 SSR + Cloudflare Workers + D1 + Drizzle, com esqueleto GEO fixo, API REST autenticada, fork por business × idioma e cinco ADRs canônicos sustentando as decisões. Este artigo cobre o tour técnico completo do código real, com paths e comandos verificáveis no monorepo.

Por Publicado em 14 min de leitura
Como funciona o blog template Astro do Sinkra Hub: tour técnico completo

Principais conclusões

  1. 01Template canônico vive em apps/blog-template; cada business × idioma é um fork independente em apps/blog-{business}-{lang}/.
  2. 02Stack: Astro 6 SSR + Cloudflare Workers + D1 + Drizzle, com Preact apenas na ilha de busca e zero JS desnecessário no cliente.
  3. 03Esqueleto GEO fixo emite Article + BreadcrumbList + FAQPage + SpeakableSpecification por artigo.
  4. 04API REST com Bearer auth; /api/search e /api/health são públicos. Publish dispara IndexNow + Google ping via waitUntil().
  5. 05Servido como subdiretório /blog para consolidar autoridade SEO no apex; ADR-026 documenta robots.txt Allow: / intencional.

Por que existe um template canônico em vez de cada business escrever o seu?

O hub atende quatro businesses (AllFluence, AIOX, Academia Lendária, Bilhon) com necessidade de blog. Sem template, cada business escreveria sua versão, três meses depois cada uma teria divergido em decisões fundamentais (cache, schema, sitemap), e atualizações de SEO técnico precisariam ser feitas N vezes. Com template, cada business é um fork em apps/blog-{business}-{lang}/ que herda decisões e identidade própria via wrangler.toml local.

O ADR-025 formaliza esse modelo: o template canônico é o source-of-truth, sync periódico propaga mudanças para os forks, e arquivos sinalizados como fork-specific (wrangler.toml, identidade visual, conteúdo) ficam preservados durante o sync. O ADR-023 formaliza por que cada idioma é um Worker separado em vez de um Worker poliglota: latência, simplicidade de configuração e isolamento de D1.

Que stack roda no template e por que cada peça?

O runtime é Cloudflare Workers (edge SSR). O framework é Astro 6 com adapter Cloudflare. O banco é D1 (SQLite na edge da Cloudflare) acessado via Drizzle ORM. A UI é Tailwind CSS 3 com Preact apenas na ilha de busca — todo o resto é HTML estático server-rendered, zero JavaScript desnecessário no cliente. TypeScript strict, Bun como package manager, e nodejs_compat ligado para o Drizzle conseguir importar node:async_hooks.

A escolha de Astro em vez de Next.js está formalizada no ADR-024: o adapter Cloudflare nativo da Astro entrega edge SSR com output: server sem ajuste, enquanto Next.js exige configuração mais pesada e tem cold start menos previsível em Workers. A escolha de D1 em vez de Postgres é dimensional: D1 é SQLite na edge com replicação geográfica e custo previsível para o volume típico de blog. Postgres entra quando o dataset cresce ou quando queries com JOIN pesado dominam.

Como funciona o fork por business × idioma?

O comando scripts/scaffold-blog-business.sh aiox pt-BR gera um fork pronto. Cada fork resultante é um Cloudflare Worker independente, com SITE_LANG pré-preenchido em wrangler.toml, identidade de marca via ORG_NAME, ORG_URL, ORG_LOGO_URL, e D1 próprio. Routes do Worker apontam para subpaths como {dominio}/blog/* em vez de subdomínio dedicado.

Conteúdo é nativo no idioma alvo, nunca tradução automática (ADR-027). A racional é dupla: tradução automática perde nuance e prejudica citação por LLM no idioma alvo, e o squad de produção (content-geo) é multi-fork por design — gera artigo nativo em qualquer idioma desde que receba --fork apps/blog-{business}-{lang}/ e leia o SITE_LANG. Tudo mais — schema, sitemap, llms.txt — é gerado automaticamente respeitando o SITE_LANG.

O que está no esqueleto GEO fixo de cada artigo?

Todo artigo renderizado em /{slug} tem a mesma estrutura visual: breadcrumb, H1, summary box com borda azul (marcado SpeakableSpecification), meta-linha com autor e datas, AI share buttons (Perplexity/ChatGPT/Claude/Gemini com prompt pré-preenchido), hero image, KeyTakeaways de 5 bullets, TableOfContents auto-gerado dos H2s, corpo HTML, tags, FAQ e relacionados.

JSON-LD emitido por artigo: Organization e WebSite (site-wide, vão em layouts/Base.astro); Article com image, wordCount, articleSection, keywords, autor com sameAs; BreadcrumbList; e FAQPage quando há FAQ. Os campos GEO ficam na própria tabela articles: hero_image_url, key_takeaways (JSON array), faq (JSON array de q/a), reading_time_min. As migrations 0001_geo_fields.sql e 0003_geo_squad_columns.sql contêm o schema.

Como a API REST do template está organizada?

Todos os endpoints sob /api/* exigem header Authorization: Bearer ${BLOG_KEY}, exceto /api/search e /api/health que são públicos por design. Operações disponíveis: criar artigo (entra como draft), listar com filtros, ler, atualizar, deletar e publicar. Publicar dispara IndexNow + Google sitemap ping em background via waitUntil(), sem bloquear a resposta.

Detalhes do roteamento: o middleware em src/middleware.ts faz canonicalização de host (301 de www para apex via SITE_URL), redirect de URL legada (/blog/artigos/{slug}/blog/{slug}) e auth gate dos /api/*. src/lib/runtime-env.ts centraliza o acesso ao binding (import { env } from cloudflare:workers) — a API antiga Astro.locals.runtime.env foi removida em Astro 6 e o template já está migrado.

Que endpoints especializados servem LLMs e crawlers?

Quatro endpoints específicos para retrieval de IA: (1) /sitemap.xml com lastmod dinâmico por artigo (D1 query) e hreflang quando há idiomas alternativos; (2) /llms.txt no padrão emergente de llmstxt.org — H1 do site, blockquote da descrição, listas markdown agrupadas por categoria; (3) /llms-full.txt com corpus completo dos artigos publicados, HTML stripped, citação direta sem precisar crawl; (4) /robots.txt com Allow: / total intencional.

O ADR-026 explica a decisão do Allow: /: este blog existe para maximizar exposição em search, retrieval e training corpus. Bloquear ingestão de LLM seria fechar a porta da exposição — a defesa contra mau uso é que conteúdo gated/comercial nunca entra no blog (vai para outras propriedades). Para conteúdo que existe para citação, abrir é a estratégia correta.

Por que o blog vive em /blog (subdiretório) e não em subdomínio?

O blog é servido como {seu-dominio}/blog/*, não blog.{seu-dominio}. Razão: SEO consolidado. O Google concentra autoridade no domínio raiz em vez de fragmentar entre subdomínios, então autoridade do site principal transfere para o blog (e vice-versa). A combinação base: /blog + trailingSlash: ignore em astro.config.mjs mais o helper src/lib/paths.ts centralizam todos os links internos, então mudar o base path no futuro é uma linha só.

Detalhe não-óbvio: a combinação base + trailingSlash: never quebra a rota index do Astro (404 em /blog), por isso o template usa ignore e deixa o canonical tag consolidar SEO em vez de redirect. Para os artigos, a estrutura é /blog/{slug}, não /blog/artigos/{slug} — o segmento /artigos/ foi removido em abril de 2026 porque era redundante e URLs mais curtas citam mais limpo em LLMs.

Quais decisões estão registradas em ADR e por quê?

O template tem cinco ADRs canônicos sustentando as decisões maiores: ADR-023 Multi-Site Per Language (1 Worker por business × idioma); ADR-024 Canonical Stack (Astro 6.2 + CF Workers + D1 + Drizzle); ADR-025 Template + sync flow; ADR-026 robots.txt Allow: / intencional (citation > training-protection); ADR-027 Conteúdo nativo no idioma (não tradução automática).

O critério para virar ADR é simples: a decisão tem trade-off real e é difícil de reverter. Cada ADR documenta o problema, alternativas consideradas, decisão escolhida e consequências. Quando alguém propuser "vamos traduzir automaticamente" em seis meses, o ADR-027 é a resposta documentada — ou aceita o argumento original, ou produz um ADR-027 v2 com as razões novas. Sem ADRs, decisões viram folclore e voltam à mesa toda vez que time muda.

Como o blog template do Sinkra Hub se compara com Astrowind, Ghost, Hashnode e WordPress?

Templates de blog não são todos iguais — diferem em modelo de hospedagem, vendor lock-in, esqueleto técnico para LLM e custo de operação por business. A matriz abaixo cobre as 12 dimensões mais sensíveis para quem precisa decidir entre o template canônico do hub e alternativas SaaS ou open-source.

Top-line

AtributoSinkra blog templateAstrowindGhost (SaaS)HashnodeWordPress
RenderAstro 6 SSR edgeAstro SSGNode SSRSSR proprietárioPHP SSR
BancoD1 (SQLite na edge)Markdown filesystemMySQLProprietárioMySQL
Multi-businessFork × business × idiomaManual1 instância = 1 site1 conta = 1 blogMulti-site (admin único)
LicenseOpen-source (template)MITMIT (Ghost core)SaaS proprietárioGPLv2
Vendor lock-inZero (self-host)ZeroMédio (SaaS opcional)AltoMédio (plugins)
Esqueleto GEO fixoEmbutido (KT, FAQ, Speakable)NãoNãoNãoPlugin
llms.txt + llms-full.txtEmbutidoNãoNãoNãoPlugin

Matriz por dimensão (12)

#DimensãoSinkra templateAstrowindGhostHashnodeWordPress
1Latência globalEdge sub-100msStatic (CDN)RegionalEdgeDepende host
2Schema técnico embutidoArticle+FAQPage+Breadcrumb+SpeakableArticle básicoArticle + JSON-LDArticlePlugin (Yoast/Rank Math)
3JS no cliente defaultZero (exceto busca Preact)ZeroTheme-dependentTheme-dependentTheme + plugins
4Multi-idioma per business1 Worker × business × idioma (ADR-023)i18n manual1 site cobre N idiomas1 conta = 1 idiomaPlugin (WPML/Polylang)
5Editor humanoAPI/CLI onlyFilesystem (markdown)WYSIWYG adminWYSIWYGGutenberg
6Publicação programáticaAPI REST + skill content-geoFilesystem commitAPI RESTAPI GraphQLAPI REST
7Custo previsívelPay-per-request CFStatic = $0$11–$199/mFree + paid tierHosting + plugins
8Newsletter built-inNãoNãoSimSimPlugin
9Comments built-inNãoNãoSimSimSim
10Governance commit5 ADRs canônicosTime individualSaaS ownerSaaS ownerPlugin maintainers
11Fork por idiomascaffold-blog-business.shManualNãoNãoMulti-site
12Robots.txt para LLMAllow: / explícito (ADR-026)Default (open)DefaultDefaultConfigurável

Verdict: Sinkra blog template ganha quando o cenário é multi-business, GEO-first, governance formal e custo previsível em escala. Astrowind ganha em simplicidade absoluta para 1 site sem CMS. Ghost ganha em editor humano + newsletter + comments built-in. Hashnode ganha em distribuição via comunidade dev. WordPress ganha em ecossistema de plugins. A escolha depende do trade-off entre controle técnico (Sinkra) e conveniência operacional (SaaS).

Que tech-debt o template carrega assumidamente?

O CLAUDE.md do template lista três itens de tech-debt explicitamente: (1) Tailwind 3 + @astrojs/tailwind está deprecated em Astro 6, migração para Tailwind 4 + @tailwindcss/vite é EPIC futuro; (2) CSP API nativa do Astro 6 ainda não habilitada, vira sprint dedicado; (3) Built-in Fonts API do Astro 6 não usada, ainda usa link rel preload manual.

Listar tech-debt explicitamente é parte da disciplina KISS da Constitution: .claude/rules/kiss-no-overengineering.md tem cinco gates antes de adicionar qualquer estrutura nova, e o oposto — admitir débito conhecido em vez de esconder — é o que evita que o template vire monumento intocável. Quem migrar primeiro Tailwind 4 vira proposta de PR com EPIC anexado; quem propor CSP escreve a story; o template incorpora.

#astro #cloudflare #d1 #template #blog #monorepo #adr

Perguntas frequentes

Posso trocar Astro por Next.js neste template?
Tecnicamente sim, mas perde o adapter Cloudflare nativo da Astro e o build edge-otimizado. ADR-024 documenta a decisão; substituir exige novo ADR com trade-off justificado.
Por que D1 e não Postgres?
D1 é SQLite na edge com replicação geográfica e custo previsível para o volume típico de blog. Postgres (Neon, Hyperdrive) entra quando dataset cresce ou queries com JOIN pesado dominam.
O fork precisa ser deploy independente?
Sim. Cada fork é um Cloudflare Worker próprio, com routes próprias e D1 próprio. Isso é o que dá isolamento por business e permite identidade visual independente — formalizado em ADR-023.
Como evitar drift entre forks e o template canônico?
O fluxo de sync (ADR-025) prevê pull periódico das mudanças do template para os forks. Arquivos fork-specific (wrangler.toml, identidade) ficam preservados durante o sync.
E se o LLM citar errado meu conteúdo?
Acontece. A defesa principal é o summary box com SpeakableSpecification: quanto mais auto-contido o resumo, menor a chance de paráfrase imprecisa do LLM.

Sobre o autor

Equipe Editorial

Equipe responsável pela curadoria, revisão e publicação de artigos técnicos no blog.

Seguir