Operações Astro
Fatos verificados em 2026-07.
Padrões abrangentes para desenvolvimento com o framework Astro: arquitetura de ilhas, coleções de conteúdo, estratégias de renderização, transições de visualização e implantação em múltiplas plataformas.
Árvore de Decisão de Estratégia de Renderização
Qual estratégia de renderização?
│
├─ O conteúdo é principalmente estático (blog, docs, marketing)?
│ ├─ SIM → Ele muda menos de uma vez por dia?
│ │ ├─ SIM → SSG (output: 'static')
│ │ │ TTFB mais rápido, armazenável em cache CDN, custo de execução zero
│ │ └─ NÃO → Híbrido (output: 'hybrid')
│ │ Padrão estático + SSR opcional por rota
│ └─ NÃO → Cada página precisa de personalização?
│ ├─ SIM → SSR (output: 'server')
│ │ Dinâmico por requisição, ciente de autenticação, dados em tempo real
│ └─ NÃO → Híbrido (output: 'hybrid')
│ Shell estático + ilhas de servidor para partes dinâmicas
│
├─ A aplicação precisa de interatividade em tempo real (dashboard, SPA)?
│ ├─ SIM → É uma SPA completa com roteamento no lado do cliente?
│ │ ├─ SIM → Considere uma SPA React/Vue, ou Astro + client:only
│ │ └─ NÃO → Híbrido + arquitetura de ilhas
│ │ Ilhas interativas em páginas estáticas
│ └─ NÃO → SSG (output: 'static')
│
├─ Preocupações com tempo de build (>10k páginas)?
│ ├─ SIM → Híbrido com renderização sob demanda
│ │ Pré-renderizar páginas populares, SSR para a cauda longa
│ └─ NÃO → SSG lida bem
│
└─ Precisa de computação de borda (baixa latência global)?
├─ SIM → SSR + adaptador Cloudflare/Vercel Edge
└─ NÃO → SSR + adaptador Node ou SSG
Configuração
// astro.config.mjs
import { defineConfig } from 'astro/config';
// SSG (padrão) - todas as páginas pré-renderizadas no build
export default defineConfig({
output: 'static',
});
// SSR - todas as páginas renderizadas na requisição
export default defineConfig({
output: 'server',
adapter: cloudflare(), // ou vercel(), netlify(), node()
});
// Híbrido - estático por padrão, SSR opt-in por página
export default defineConfig({
output: 'hybrid',
adapter: cloudflare(),
});
---
// No modo híbrido, opte POR NÃO pré-renderizar páginas específicas:
export const prerender = false;
// No modo SSR, opte POR pré-renderizar:
export const prerender = true;
---
Referência Rápida da Arquitetura de Ilhas
| Diretiva | Hidrata Quando | JS Enviado | Caso de Uso |
|---|---|---|---|
client:load | Imediatamente ao carregar a página | Pacote completo | Interatividade acima da dobra (nav, CTA do hero) |
client:idle | Após a página ficar ociosa (requestIdleCallback) | Pacote completo | Interatividade abaixo da dobra (formulário de comentário, chat) |
client:visible | Quando rolado para a viewport | Pacote completo | Longe no final da página (widget do rodapé, carrossel) |
client:media | Quando a media query corresponde | Pacote completo | Nav apenas para mobile, componentes responsivos |
client:only="react" | Imediatamente, pula SSR completamente | Pacote completo | Componentes que não podem fazer SSR (canvas, WebGL) |
| (nenhuma) | Nunca - apenas HTML estático | Zero JS | Conteúdo estático, cards, cabeçalhos |
---
import NavBar from '../components/NavBar.tsx';
import CommentForm from '../components/CommentForm.tsx';
import ImageCarousel from '../components/ImageCarousel.svelte';
import MobileMenu from '../components/MobileMenu.vue';
import ThreeScene from '../components/ThreeScene.tsx';
---
<!-- Carrega imediatamente - interatividade crítica -->
<NavBar client:load />
<!-- Carrega após a página ficar ociosa - não crítico -->
<CommentForm client:idle />
<!-- Carrega quando rolado para a viewport - lazy -->
<ImageCarousel client:visible />
<!-- Carrega apenas no mobile -->
<MobileMenu client:media="(max-width: 768px)" />
<!-- Apenas no cliente, sem SSR (WebGL não roda no servidor) -->
<ThreeScene client:only="react" />
Guia Rápido de Coleções de Conteúdo
Definir Schema
// src/content.config.ts (Astro 5) ou src/content/config.ts (Astro 4)
import { defineCollection, z, reference } from 'astro:content';
import { glob } from 'astro/loaders';
const blog = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
description: z.string().max(160),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: z.string().optional(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
author: reference('authors'), // Referenciar outra coleção
}),
});
const authors = defineCollection({
loader: glob({ pattern: '**/*.json', base: './src/content/authors' }),
schema: z.object({
name: z.string(),
avatar: z.string(),
bio: z.string(),
socials: z.object({
twitter: z.string().optional(),
github: z.string().optional(),
}).optional(),
}),
});
export const collections = { blog, authors };
Consultar Coleções
---
import { getCollection, getEntry } from 'astro:content';
// Obter todos os posts do blog que não são rascunho, ordenados por data
const posts = (await getCollection('blog', ({ data }) => !data.draft))
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
// Obter uma única entrada
const post = await getEntry('blog', 'my-first-post');
// Resolver uma referência
const author = await getEntry(post.data.author);
// Renderizar conteúdo
const { Content, headings } = await post.render();
---
<Content />
Coleções de Conteúdo vs CMS Externo
| Critério | Coleções de Conteúdo | CMS Externo (Payload, etc.) |
|---|---|---|
| Tipo de conteúdo | Markdown/MDX local, docs, blogs | Modelos de dados relacionais |
| Autores | Desenvolvedores (versionado) | Editores (admin UI, autenticação multiusuário) |
| Validação | Type-safe via Zod no build | Schemas do CMS + contratos de API |
| Cadência de atualização | Implanta com o site | Independente de implantações |
| Necessidades de API | Nenhuma (consultas em tempo de build) | REST/GraphQL para outros consumidores |
| Fluxo de trabalho | PRs Git, revisão simples | Fluxos editoriais, rascunhos, papéis |
Regra geral: comece com Coleções de Conteúdo; recorra a um CMS apenas quando não-desenvolvedores precisarem publicar sem uma implantação, ou quando o conteúdo for genuinamente relacional.
Referência de Estrutura do Projeto
project-root/
├── astro.config.mjs # Configuração do Astro
├── tsconfig.json # Configuração TypeScript (estende astro/tsconfigs)
├── package.json
├── public/ # Assets estáticos (copiados como estão)
│ ├── favicon.svg
│ ├── robots.txt
│ └── og-image.png
├── src/
│ ├── pages/ # Roteamento baseado em arquivos
│ │ ├── index.astro # → /
│ │ ├── about.astro # → /about
│ │ ├── blog/
│ │ │ ├── index.astro # → /blog
│ │ │ └── [slug].astro # → /blog/:slug (dinâmico)
│ │ ├── api/
│ │ │ └── search.ts # → /api/search (endpoint de API)
│ │ └── [...slug].astro # → catch-all/404
│ ├── layouts/
│ │ ├── BaseLayout.astro # Shell HTML, <head>, estilos globais
│ │ └── BlogPost.astro # Layout de post do blog
│ ├── components/
│ │ ├── Header.astro # Componente Astro estático
│ │ ├── Footer.astro
│ │ ├── NavBar.tsx # Ilha React
│ │ └── Counter.svelte # Ilha Svelte
│ ├── content/ # Arquivos fonte das coleções de conteúdo
│ │ ├── blog/
│ │ │ ├── post-one.md
│ │ │ └── post-two.mdx
│ │ └── authors/
│ │ └── jane.json
│ ├── content.config.ts # Schemas das coleções (Astro 5)
│ ├── middleware.ts # Middleware de requisição/resposta
│ ├── styles/
│ │ └── global.css
│ └── lib/ # Utilitários compartilhados
│ ├── utils.ts
│ └── constants.ts
└── .env # Variáveis de ambiente
Referência Rápida de Transições de Visualização
---
// src/layouts/BaseLayout.astro
import { ViewTransitions } from 'astro:transitions';
---
<html>
<head>
<ViewTransitions />
</head>
<body>
<slot />
</body>
</html>
Diretivas de Transição
<!-- Persistir elemento entre páginas (mantém estado, evita re-renderização) -->
<audio transition:persist id="player">
<source src="/music.mp3" />
</audio>
<!-- Transição nomeada para pareamento de animação -->
<img transition:name="hero" src={post.heroImage} />
<!-- Animação personalizada -->
<div transition:animate="slide">Conteúdo</div>
<div transition:animate="fade">Conteúdo</div>
<div transition:animate="none">Sem animação</div>
<!-- Persistir com nome (para múltiplos elementos persistentes) -->
<video transition:persist="media-player" />
Eventos do Ciclo de Vida
<script>
document.addEventListener('astro:before-preparation', (e) => {
// Antes da nova página ser buscada - cancelar navegação, mostrar carregamento
});
document.addEventListener('astro:after-preparation', (e) => {
// Nova página buscada, antes da troca
});
document.addEventListener('astro:before-swap', (e) => {
// Personalizar comportamento da troca de DOM
});
document.addEventListener('astro:after-swap', () => {
// DOM atualizado - reinicializar scripts
});
document.addEventListener('astro:page-load', () => {
// Página totalmente carregada (dispara na inicial e em cada navegação)
// Use este evento em vez de DOMContentLoaded com View Transitions
});
</script>
Manipulação de Voltar/Avançar
// astro.config.mjs
export default defineConfig({
prefetch: {
prefetchAll: true, // Pré-buscar todos os links ao passar o mouse
defaultStrategy: 'hover', // 'hover' | 'tap' | 'viewport' | 'load'
},
});
<!-- Controle de pré-busca por link -->
<a href="/about" data-astro-prefetch>Pré-buscar ao passar o mouse (padrão)</a>
<a href="/blog" data-astro-prefetch="viewport">Pré-buscar quando visível</a>
<a href="/contact" data-astro-prefetch="load">Pré-buscar imediatamente</a>
<a href="/external" data-astro-prefetch="false">Sem pré-busca</a>
Árvore de Decisão de Implantação
Onde implantar?
│
├─ Precisa de computação de borda + ecossistema Cloudflare (KV, D1, R2)?
│ └─ Cloudflare Pages/Workers
│ Adaptador: @astrojs/cloudflare
│ Melhor para: Borda global, Workers bindings, custo-benefício
│
├─ Precisa de serverless + ecossistema Vercel (ISR, analytics)?
│ └─ Vercel
│ Adaptador: @astrojs/vercel
│ Melhor para: Migração de Next.js, otimização de imagens, ISR
│
├─ Precisa de serverless + ecossistema Netlify (formulários, identidade)?
│ └─ Netlify
│ Adaptador: @astrojs/netlify
│ Melhor para: JAMstack, formulários integrados, testes A/B
│
├─ Precisa de controle total do servidor (Docker, runtime personalizado)?
│ └─ Node.js (autônomo ou Express/Fastify)
│ Adaptador: @astrojs/node
│ Melhor para: Auto-hospedado, WebSocket, processos de longa duração
│
└─ Site puramente estático (sem necessidade de SSR)?
└─ Qualquer host estático (GitHub Pages, S3, Cloudflare Pages)
Nenhum adaptador necessário, output: 'static'
Melhor para: Blogs, docs, sites de marketing
Instalação do Adaptador
# Cloudflare
npx astro add cloudflare
# Vercel
npx astro add vercel
# Netlify
npx astro add netlify
# Node.js
npx astro add node
Pegadas Comuns
| Pegada | Por quê | Correção |
|---|---|---|
| Erros de incompatibilidade de hidratação | HTML do servidor difere da renderização do cliente (datas, IDs aleatórios, APIs do navegador) | Use client:only para componentes que dependem do navegador, ou garanta renderização determinística |
import.meta.env indefinido no cliente | Apenas variáveis prefixadas com PUBLIC_ são expostas ao código do lado do cliente | Renomeie para PUBLIC_MY_VAR ou passe via props do servidor |
| Rotas dinâmicas 404 no SSG | getStaticPaths() não retornando todos os parâmetros possíveis | Garanta que getStaticPaths() retorne todos os caminhos válidos, ou mude para híbrido/SSR |
| Imagens não otimizando | Usando <img> em vez do componente <Image /> do Astro | Importe de astro:assets: import { Image } from 'astro:assets' e use imports locais para src |
| SSR falha sem adaptador | output: 'server' ou 'hybrid' requer um adaptador de implantação | Instale o adaptador: npx astro add cloudflare (ou vercel, netlify, node) |
| Componentes MDX não renderizando | Componentes personalizados não passados para o conteúdo MDX | Passe componentes via <Content components={{ MyComponent }} /> ou use a configuração MDX em astro.config.mjs |
| Alterações no schema da coleção de conteúdo não refletidas | Geração de tipos está em cache, tipos .astro desatualizados | Execute astro sync para regenerar tipos, reinicie o servidor de desenvolvimento |
client:* em componentes Astro | Diretivas de cliente só funcionam em componentes de framework (React, Vue, Svelte) | Componentes Astro são apenas estáticos; extraia partes interativas para um componente de framework |
document / window não está definido | Código do lado do servidor não pode acessar globais do navegador | Proteja com if (typeof window !== 'undefined') ou mova para client:only |
| Estilos vazando entre componentes | Usando CSS global em vez de estilos com escopo | Use <style> (com escopo por padrão em .astro) ou <style is:global> intencionalmente |
| Transições de Visualização quebram scripts | DOMContentLoaded só dispara uma vez com View Transitions | Use o evento astro:page-load, que dispara em cada navegação |
| Variáveis de ambiente ausentes em produção | .env não carregado ou variáveis de ambiente da plataforma não configuradas | Use envField no astro.config.mjs para validação; defina variáveis no painel da plataforma |
Checklist de Segurança para Produção
Para cada implantação em produção, aborde:
- Headers CSP - configure uma
Content-Security-Policyrestritiva (veja padrões de middleware emreferences/deployment.md) - Restrições de imagem remota - imponha listas de permissão explícitas de
image.domains/remotePatterns; nunca derive URLs de imagem de entrada do usuário (risco de SSRF) - Validação do header Host - verifique se o host da requisição corresponde aos domínios esperados no middleware (apenas SSR/híbrido)
- Gerenciamento de segredos - no Cloudflare, use Workers Bindings (
wrangler secret put), não variáveis de ambiente embutidas no código; em outras plataformas, use armazenamentos de segredos da plataforma - Apenas HTTPS - garanta que todos os recursos externos (scripts, imagens, fontes) carreguem sobre HTTPS
- Validação de entrada - sanitize toda entrada do usuário em contextos SSR (parâmetros de query, corpos de formulário, cookies)
Arquivos de Referência
| Arquivo | Conteúdo | Linhas |
|---|---|---|
references/content-collections.md | Padrões de schema, tipos Zod, consultas, MDX, camada de conteúdo, migrações | ~500 |
| `references/islands-rendering |
...
[truncado para tradução]