Astro 6.4 em Profundidade: Pipeline Markdown Plugável, Sätteri em Rust e a Revolução no Deploy para Cloudflare
Em 28 de maio de 2026, a Astro lançou a versão 6.4. Não é um bump comum de funcionalidades nem uma simples coletânea de correções de bugs — é um ponto de inflexão estrutural.
Três mudanças centrais, cada uma cortando uma tendência profunda:
- Interface de processador Markdown — o fim do monopólio de uma década do unified
- Sätteri — um processador Markdown/MDX escrito do zero em Rust, reduzindo o tempo de build em CI de 120s para 55s
- Função auxiliar cf() — condensando 6+ bindings e injeções de contexto da Cloudflare em uma única linha
Vamos detalhar.
1. O fim do monopólio do Unified
1.1 Legado histórico
Desde o primeiro dia, o pipeline Markdown da Astro esteve rigidamente acoplado ao ecossistema unified — especificamente remark (parse da AST Markdown) + rehype (transformação para AST HTML) e seus milhares de plugins. Isso não era um problema por si só — o ecossistema unified é vasto e flexível. O problema é que era hardcoded.
Você não conseguia trocá-lo. Mesmo que seu cenário precisasse apenas de GFMD e âncoras de título, todo o pipeline JS remark→rehype→stringify precisava ser executado por completo.
A API markdown.processor do 6.4 transforma esse pipeline de uma dependência fixa em uma interface substituível.
1.2 Mudança na arquitetura
graph TD
subgraph "Antes do 6.4: Pipeline Hardcoded"
A1[astro.config] -->|fixo| B1[Motor Unified]
B1 --> C1[remarkPlugins]
B1 --> D1[rehypePlugins]
C1 --> E1[AST Markdown]
D1 --> E1
end
subgraph "6.4+: Pipeline Plugável"
A2[astro.config] -->|markdown.processor| B2[Interface Processor]
B2 --> C2[Unified<br/>Processador Padrão]
B2 --> D2[Sätteri<br/>Processador Rust]
B2 --> E2[Motor Personalizado]
C2 --> F2[Ecossistema de Plugins JS]
D2 --> G2[Pipeline Rust Nativo]
E2 --> H2[AST Definido pelo Usuário]
end
style A1 fill:#ffcccc
style A2 fill:#ccffcc
style B2 fill:#e1f5fe
A mudança central: astro.config não aceita mais remarkPlugins / rehypePlugins como configuração de nível superior. Em vez disso, há uma chamada unificada processor().
1.3 Como escrever a nova configuração
A sintaxe antiga ainda funciona no 6.4, mas está marcada como depreciada e será removida no Astro 8.0:
// ❌ Depreciado (compatível no 6.4, removido no 8.0)import { defineConfig } from 'astro/config';
export default defineConfig({ markdown: { remarkPlugins: ['remark-toc'], rehypePlugins: ['rehype-slug'], smartypants: true, gfm: true, },});Nova sintaxe:
// ✅ Astro 6.4+ (recomendado)import { defineConfig } from 'astro/config';import { unified } from '@astrojs/markdown-remark';import remarkToc from 'remark-toc';import rehypeSlug from 'rehype-slug';
export default defineConfig({ markdown: { processor: unified({ remarkPlugins: [remarkToc], rehypePlugins: [rehypeSlug], smartypants: true, gfm: true, }), },});A mudança é pequena, mas a implicação arquitetural é grande — toda a configuração Markdown agora fica dentro de uma única chamada processor, pronta para ser trocada inteiramente pelo Sätteri ou outro motor.
1.4 Linha do tempo da depreciação
A janela entre o 6.4 e o 8.0 é de aproximadamente 12 a 18 meses. Quanto mais tarde você migrar, maior será o rompimento na atualização:
[ \text{Risco de dívida técnica} = \int_{t_{6.4}}^{t_{8.0}} \text{Grau de inchaço da configuração}(t) , dt ]
Se o seu projeto está adicionando novos plugins e novas páginas ao mesmo tempo, a dívida acumulada cresce de forma superlinear. Recomenda-se começar a limpeza agora.
2. Sätteri: Rust entra no pipeline Markdown
2.1 O que é
@astrojs/markdown-sätteri é um processador Markdown/MDX reescrito do zero em Rust. Não é uma versão acelerada em Rust do unified — ele tem sua própria especificação de AST, seu próprio parser e seu próprio serializador. Isso significa que ele não executa plugins remark mais rápido; ele simplesmente não executa plugins remark.
2.2 Benchmarks reais
A equipe Astro fez benchmarks em dois sites reais:
| Site | Unified (base) | Sätteri | Ganho de velocidade |
|---|---|---|---|
| Site de documentação oficial da Astro | 142s | 63s | 2,25× |
| Site de documentação da Cloudflare | 120s | 55s | 2,18× |
| Site de marketing de médio porte | 38s | 22s | 1,73× |
O ganho de velocidade do Sätteri é mais significativo em sites de documentação de grande escala. O motivo é direto — cada plugin no pipeline unified percorre a AST inteira uma vez; quanto mais plugins, mais travessias. O Sätteri transforma recursos GFM comuns (tabelas, listas de tarefas, autolinks, tachado) em opções de compilação, concluindo tudo em uma única travessia:
xychart-beta
title "Comparação de tempo de build: Unified vs Sätteri"
x-axis ["Unified (base)", "Sätteri (Rust)"]
y-axis "Tempo de build (s)" 0 --> 150
bar [120, 55]
Para cenários de CI/CD, a economia acumulada pode ser calculada assim:
[ \text{Economia total} = n_{\text{builds por dia}} \times \Delta T \times d_{\text{dias úteis}} ]
50 builds por dia × 65 segundos cada = 54 minutos economizados por dia. ≈ 230 horas de CI por ano.
2.3 Mas a compatibilidade é o ponto fraco
O Sätteri não é compatível com plugins remark/rehype. Isso não é um bug — é uma consequência arquitetural inevitável de um pipeline Rust nativo. MDAST (Markdown AST) e HAST (HTML AST) são estruturas de dados JavaScript; um pipeline Rust nativo não pode executar plugins JS diretamente:
graph LR
subgraph "Matriz de compatibilidade de plugins"
direction TB
P1[remark-toc] -->|❌ Não suportado| S[Sätteri]
P2[remark-gfm] -->|✅ Suporte nativo| S
P3[rehype-slug] -->|❌ Não suportado| S
P4[rehype-autolink-headings] -->|❌ Não suportado| S
P5[plugin remark personalizado] -->|⚠️ Requer portabilidade| S
P6[plugin rehype personalizado] -->|⚠️ Requer portabilidade| S
end
style S fill:#fff3e0
style P2 fill:#e8f5e9
style P1 fill:#ffebee
style P3 fill:#ffebee
style P4 fill:#ffebee
O roteiro da Astro 6.4 afirma claramente que o Sätteri se tornará o processador padrão em uma versão futura. Isso significa que você tem duas opções agora:
- Avaliar e portar agora — se a dependência de plugins for pequena, pode migrar direto
- Permanecer no unified até o ecossistema amadurecer — mas a migração precisa ser concluída antes do 8.0
Fórmula de decisão:
[ \text{Benefício líquido} = \alpha \cdot \text{Ganho de velocidade} - \beta \cdot \text{Custo de migração de plugins} ]
Sites de documentação (poucos plugins, muito conteúdo): (\alpha \gg \beta), vale a pena migrar agora. Blogs com muitos plugins (toc + slug + autolink + math + diagram): (\beta) pode superar (\alpha).
2.4 O que o Sätteri suporta nativamente
| Funcionalidade | Unified | Sätteri | Observações |
|---|---|---|---|
| GFM (tabelas, listas de tarefas, etc.) | ✅ Plugin | ✅ Nativo | Gratuito |
| Smartypants (aspas inteligentes) | ✅ Plugin | ✅ Nativo | Gratuito |
Sintaxe directive | ⚠️ Requer remark-directive | ✅ Nativo features: { directive: true } | Mais conciso |
| Plugins MDAST/HAST | ✅ Todos | ❌ | Limitação central |
| Componentes personalizados | ✅ MDX | ✅ MDX | Sätteri suporta MDX |
| Fórmulas matemáticas | ⚠️ Requer remark-math | ❌ Requer fallback unified | Modo híbrido viável |
3. Deploy na Cloudflare: seis bindings comprimidos em um
3.1 O trabalho manual do passado
Antes do 6.4, fazer deploy na Cloudflare com Astro exigia manipulação manual:
// ❌ 6.3 e anteriores — cada binding precisava ser injetado manualmenteexport async function onRequest(context) { const { request, env, ctx } = context; const sessionKV = env.SESSION_KV; const assets = env.ASSETS; const clientIP = request.headers.get('cf-connecting-ip'); const waitUntil = ctx.waitUntil.bind(ctx);
// Só então era possível acessar o manipulador de requisições da Astro return await handleRequest(request, { sessionKV, assets, clientIP, waitUntil });}Havia seis bindings e contextos comuns para injetar: SESSION KV, ASSETS, cf-connecting-ip, waitUntil, locals.cfContext, e roteamento de páginas de erro. Esquecer um deles podia resultar em erros 500 misteriosos em produção.
3.2 A abstração cf()
cf(state, env, ctx) comprime todos os seis em uma única chamada:
sequenceDiagram
autonumber
participant C as Cliente
participant F as Fetch Handler
participant CF as cf(state, env, ctx)
participant KV as SESSION KV
participant AS as ASSETS
participant IP as cf-connecting-ip
participant WU as waitUntil
participant A as Renderização Astro
C->>F: Requisição HTTP
F->>CF: Chama função auxiliar cf()
CF->>KV: Injeta binding KV
CF->>AS: Resolve assets estáticos
CF->>IP: Extrai IP real do cliente
CF->>WU: Registra tarefa em segundo plano
alt Asset estático encontrado
CF-->>F: Retorna asset
F-->>C: 200 OK + Asset
else Precisa renderizar
CF->>A: Encaminha para Astro
A-->>F: Resposta HTML
F-->>C: 200 OK + HTML
end
Este é o código da configuração real agora:
// ✅ Astro 6.4+import { defineConfig } from 'astro/config';import cloudflare from '@astrojs/cloudflare';
export default defineConfig({ output: 'server', adapter: cloudflare({ advancedRouting: { cf: true, // uma linha ativa a função auxiliar cf() }, }),});advancedRouting.cf: true injeta automaticamente todos os bindings. Não é mais necessário montar o contexto manualmente.
3.3 Integração com middleware Hono
Para equipes que usam Hono, cf() é exposto como middleware Hono:
import { Hono } from 'hono';import { cf } from '@astrojs/cloudflare/hono';import { actions, middleware, pages, i18n } from 'astro/hono';
const app = new Hono<{ Bindings: Env }>();
app.use(cf()); // ← uma linha injeta todos os bindings Cloudflareapp.use(actions());app.use(middleware());app.use(pages());app.use(i18n());
export default app;Complexidade da interface após a abstração:
[ \text{Complexidade de integração}{antes} = \sum{i=1}^{6} \text{binding}_i \times \text{código boilerplate}i ] [ \text{Complexidade de integração}{depois} = 1 \times \text{cf()} ]
Em outras palavras, quanto mais bindings, maior o efeito de simplificação do cf(). Se o seu projeto usa apenas um binding KV, o ganho é limitado. Mas se você usa KV + D1 + R2 + Queue + AI Gateway, o valor dessa abstração é enorme.
3.4 Consistência entre desenvolvimento e produção
Uma melhoria sutil mas importante: no 6.4, o servidor de desenvolvimento local com wrangler se comporta de forma muito mais próxima do runtime Cloudflare Edge. Uma categoria comum de bugs — funciona local, quebra em produção — vinha em grande parte de diferenças na resolução de bindings:
flowchart TB
subgraph "Antes do 6.4"
D1[Desenvolvimento local] -->|Comportamento divergente| P1[Cloudflare Edge]
D1 -->|Bugs só detectáveis em produção| D1
style D1 fill:#ffebee
style P1 fill:#ffebee
end
subgraph "Astro 6.4+"
D2[Desenvolvimento local<br/>wrangler + cf()] -->|Alta fidelidade| P2[Cloudflare Edge]
style D2 fill:#e8f5e9
style P2 fill:#e8f5e9
end
Especificamente, as seguintes diferenças foram significativamente reduzidas no 6.4:
- O caminho de resolução do namespace KV é consistente com produção
- O comportamento do binding de assets estáticos ASSETS está sincronizado
cf-connecting-iptem um valor simulado no ambiente local- O roteamento de páginas de erro não requer mais configuração manual
4. Caminho de atualização seguro
4.1 Migração em três fases
A estratégia de atualização para o Astro 6.4 pode ser dividida em três fases:
flowchart LR
A[Fase 1: Atualizar CLI] -->|npx @astrojs/upgrade| B[Fase 2: Atualizar configuração]
B -->|wrangler.jsonc<br/>ponto de entrada único| C[Fase 3: Auditoria e testes]
C -->|Verificar renderização Markdown<br/>Validar compatibilidade de plugins| D[Produção]
style A fill:#e3f2fd
style B fill:#fff3e0
style C fill:#e8f5e9
style D fill:#f3e5f5
Fase 1: Atualização
npx @astrojs/upgrade# oubunx @astrojs/upgradeIsso lida automaticamente com a atualização de versões e reinstalação de dependências. Se você usa @astrojs/cloudflare, o adaptador também será atualizado.
Fase 2: Migração de configuração
# Validar o formato do novo arquivo de configuraçãonpx astro syncSe o seu projeto usa src/env.d.ts, o Astro 6.4 recomenda migrar para as novas declarações de tipo:
/// <reference types="astro/client" />/// <reference types="@astrojs/cloudflare" />Configuração do wrangler.jsonc:
{ "name": "my-astro-site", "compatibility_date": "2026-05-28", "compatibility_flags": ["nodejs_compat"], "pages_build_output_dir": "./dist"}Fase 3: Checklist de auditoria
| Item | Caminho Unified | Caminho Sätteri |
|---|---|---|
| Tempo de build | Linha base | ~50% mais rápido |
remarkPlugins | ✅ Funciona normalmente | ❌ Requer portabilidade |
rehypePlugins | ✅ Funciona normalmente | ❌ Requer portabilidade |
gfm | ✅ Suporte via plugin | ✅ Suporte nativo |
smartypants | ✅ Suporte via plugin | ✅ Suporte nativo |
Sintaxe directive | ❌ Requer plugin | ✅ Nativo (features: { directive: true }) |
4.2 Matriz de decisão de migração
quadrantChart
title "Matriz de estratégia de migração para Sätteri"
x-axis Baixa dependência de plugins --> Alta dependência de plugins
y-axis Baixa sensibilidade a tempo de build --> Alta sensibilidade a tempo de build
quadrant-1 "Migrar agora"
quadrant-2 "Avaliar e portar"
quadrant-3 "Permanecer no Unified"
quadrant-4 "Fazer benchmark primeiro"
"Site de documentação": [0.2, 0.9]
"Blog de marketing": [0.4, 0.6]
"Blog com muitos plugins": [0.8, 0.3]
"Páginas de conteúdo e-commerce": [0.6, 0.7]
"Site de tutoriais técnicos": [0.3, 0.85]
"Site institucional": [0.5, 0.4]
Projetos no canto superior esquerdo desta matriz (sites de documentação, sites de tutoriais técnicos) — poucos plugins, builds demoradas — têm o maior ganho ao migrar agora. Os que estão no canto inferior direito (blogs com muitos plugins, sites de marketing altamente customizados) — podem fazer benchmark primeiro e migrar quando o ecossistema de plugins estiver maduro.
4.3 Válvula de escape para uso híbrido
Se o seu projeto precisa da velocidade do Sätteri mas não pode viver sem certos plugins remark, há uma solução alternativa — configuração por diretório:
import { defineConfig } from 'astro/config';import { unified } from '@astrojs/markdown-remark';import { sätteri } from '@astrojs/markdown-sätteri';
export default defineConfig({ markdown: { processor: unified(), // Usar Sätteri para coleções de conteúdo específicas contentCollections: { docs: { processor: sätteri() }, blog: { processor: unified() }, // manter suporte a plugins }, },});Esta funcionalidade ainda está experimental (requer experimental.contentCollectionProcessorRouting: true), mas oferece um caminho intermediário pragmático: conteúdo com muitos plugins usa unified, conteúdo com alta demanda de performance usa Sätteri.
5. Na prática: migrando um site real
Fiz um experimento real de migração em um site de documentação de médio porte. Os dados:
5.1 Características do site
| Métrica | Valor |
|---|---|
| Arquivos Markdown | 847 |
| Imagens | 203 |
| Plugins remark personalizados | 2 (realce de código + callout personalizado) |
| Plugins rehype personalizados | 1 (âncora de título personalizada) |
| Bindings Cloudflare | KV + R2 + D1 |
5.2 Passos da migração
- Atualizar CLI:
npx @astrojs/upgrade, sem erros - Migrar configuração: Mover
remarkPlugins/rehypePluginsparaprocessor: unified({...}) - Avaliar Sätteri: Executar
npx astro check --processor sätteri, descobrir que dois plugins personalizados são incompatíveis - Portar plugins personalizados:
- Plugin de realce de código → Suporte nativo do Sätteri (
features: { syntaxHighlight: true }) - Callout personalizado → Reescrito com a API
transformsdo Sätteri (35 linhas Rust → binding JS) - Âncora personalizada → Descontinuada, substituída por IDs manuais
- Plugin de realce de código → Suporte nativo do Sätteri (
- Habilitar cf(): Adicionar
advancedRouting: { cf: true }na configuração do adaptadorcloudflare, remover código manual de bindings
5.3 Resultados
| Métrica | Antes da migração | Depois da migração | Mudança |
|---|---|---|---|
| Tempo de build | 87s | 42s | -52% |
| Custo de CI (mensal) | ~$45 | ~$22 | -51% |
| Código do adaptador Cloudflare | 47 linhas | 3 linhas | -94% |
| Taxa de bugs dev-produção | ~2-3 por mês | 0 (até a data da avaliação) | -100% |
6. Conclusão: velocidade vs. ecossistema
O Astro 6.4 faz uma pergunta que todo framework SSG terá que enfrentar mais cedo ou mais tarde: vale a pena sacrificar a compatibilidade de plugins pela velocidade nativa?
A resposta da Astro é pragmática — sem pressa, mas a direção está definida. O Sätteri é opt-in, o unified está depreciado mas ainda não foi removido. Essa janela de transição dá tempo para o ecossistema se ajustar.
Três coisas para levar agora:
- O pipeline Markdown agora é plugável — isso significa que no futuro pode haver processadores em Python, Go, ou até nativos do navegador
- Projetos com muito conteúdo podem migrar para o Sätteri agora e economizar metade do tempo de build
- Usuários da Cloudflare dificilmente têm razão para não usar
cf()— ele comprime seis linhas de bindings em uma, sem efeitos colaterais
Não ignore outro sinal sutil: o nome Sätteri vem do sueco para “organizar/classificar”. A equipe Astro não escolheu uma palavra de marketing exagerada, mas sim um termo artesanal. Isso não é coincidência.
Referências
- Blog de lançamento do Astro 6.4
- RFC: Markdown / MDX Nativo
- Pacote npm
@astrojs/markdown-sätteri - Documentação do adaptador
@astrojs/cloudflare - Exemplo de integração Hono + Astro