needhelp
← Back to blog

Astro 6.4 em Profundidade: Pipeline Markdown Plugável, Sätteri em Rust e a Revolução no Deploy para Cloudflare

by needhelp
Astro
Frontend
Rust
Cloudflare
Markdown
SSG
Desenvolvimento Web

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:

SiteUnified (base)SätteriGanho de velocidade
Site de documentação oficial da Astro142s63s2,25×
Site de documentação da Cloudflare120s55s2,18×
Site de marketing de médio porte38s22s1,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:

  1. Avaliar e portar agora — se a dependência de plugins for pequena, pode migrar direto
  2. 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

FuncionalidadeUnifiedSätteriObservações
GFM (tabelas, listas de tarefas, etc.)✅ Plugin✅ NativoGratuito
Smartypants (aspas inteligentes)✅ Plugin✅ NativoGratuito
Sintaxe directive⚠️ Requer remark-directive✅ Nativo features: { directive: true }Mais conciso
Plugins MDAST/HAST✅ TodosLimitação central
Componentes personalizados✅ MDX✅ MDXSätteri suporta MDX
Fórmulas matemáticas⚠️ Requer remark-math❌ Requer fallback unifiedModo 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 manualmente
export 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 Cloudflare
app.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-ip tem 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

Terminal window
npx @astrojs/upgrade
# ou
bunx @astrojs/upgrade

Isso 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

Terminal window
# Validar o formato do novo arquivo de configuração
npx astro sync

Se 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

ItemCaminho UnifiedCaminho Sätteri
Tempo de buildLinha 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étricaValor
Arquivos Markdown847
Imagens203
Plugins remark personalizados2 (realce de código + callout personalizado)
Plugins rehype personalizados1 (âncora de título personalizada)
Bindings CloudflareKV + R2 + D1

5.2 Passos da migração

  1. Atualizar CLI: npx @astrojs/upgrade, sem erros
  2. Migrar configuração: Mover remarkPlugins / rehypePlugins para processor: unified({...})
  3. Avaliar Sätteri: Executar npx astro check --processor sätteri, descobrir que dois plugins personalizados são incompatíveis
  4. Portar plugins personalizados:
    • Plugin de realce de código → Suporte nativo do Sätteri (features: { syntaxHighlight: true })
    • Callout personalizado → Reescrito com a API transforms do Sätteri (35 linhas Rust → binding JS)
    • Âncora personalizada → Descontinuada, substituída por IDs manuais
  5. Habilitar cf(): Adicionar advancedRouting: { cf: true } na configuração do adaptador cloudflare, remover código manual de bindings

5.3 Resultados

MétricaAntes da migraçãoDepois da migraçãoMudança
Tempo de build87s42s-52%
Custo de CI (mensal)~$45~$22-51%
Código do adaptador Cloudflare47 linhas3 linhas-94%
Taxa de bugs dev-produção~2-3 por mês0 (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:

  1. O pipeline Markdown agora é plugável — isso significa que no futuro pode haver processadores em Python, Go, ou até nativos do navegador
  2. Projetos com muito conteúdo podem migrar para o Sätteri agora e economizar metade do tempo de build
  3. 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

Share this page