Astro Web Framework
Visão Geral
Astro é um framework web projetado para sites ricos em conteúdo — blogs, documentações, portfólios, sites de marketing e e-commerce. Sua inovação central é a Arquitetura de Ilhas: por padrão, o Astro envia zero JavaScript para o navegador. Componentes interativos são hidratados seletivamente como "ilhas" isoladas. Astro suporta React, Vue, Svelte, Solid e outros frameworks de UI simultaneamente no mesmo projeto, permitindo que você escolha a ferramenta certa para cada componente.
Quando Usar Esta Skill
- Use ao construir um blog, site de documentação, página de marketing ou portfólio
- Use quando performance e Core Web Vitals forem a prioridade máxima
- Use quando o projeto for pesado em conteúdo com arquivos Markdown ou MDX
- Use quando você quiser saída SSG (estática) com SSR opcional para rotas dinâmicas
- Use quando o usuário perguntar sobre arquivos
.astro,Astro.props, coleções de conteúdo ou diretivasclient:
Como Funciona
Passo 1: Configuração do Projeto
npm create astro@latest meu-site
cd meu-site
npm install
npm run dev
Adicione integrações conforme necessário:
npx astro add tailwind # Tailwind CSS
npx astro add react # Suporte a componentes React
npx astro add mdx # Suporte a MDX
npx astro add sitemap # Auto sitemap.xml
npx astro add vercel # Adaptador SSR para Vercel
Estrutura do projeto:
src/
pages/ ← Roteamento baseado em arquivos (.astro, .md, .mdx)
layouts/ ← Estruturas de página reutilizáveis
components/ ← Componentes de UI (.astro, .tsx, .vue, etc.)
content/ ← Coleções de conteúdo com segurança de tipos (Markdown/MDX)
styles/ ← CSS global
public/ ← Ativos estáticos (copiados como estão)
astro.config.mjs ← Configuração do framework
Passo 2: Sintaxe de Componentes Astro
Arquivos .astro possuem uma cerca de código no topo (apenas servidor) e um template abaixo:
---
// src/components/Card.astro
// Este bloco é executado APENAS no servidor — nunca no navegador
interface Props {
title: string;
href: string;
description: string;
}
const { title, href, description } = Astro.props;
---
<article class="card">
<h2><a href={href}>{title}</a></h2>
<p>{description}</p>
</article>
<style>
/* Escopado automaticamente a este componente */
.card { border: 1px solid #eee; padding: 1rem; }
</style>
Passo 3: Páginas Baseadas em Arquivos e Roteamento
src/pages/index.astro → /
src/pages/about.astro → /about
src/pages/blog/[slug].astro → /blog/:slug (dinâmico)
src/pages/blog/[...path].astro → /blog/* (coringa)
Rota dinâmica com getStaticPaths:
---
// src/pages/blog/[slug].astro
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map(post => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<h1>{post.data.title}</h1>
<Content />
Passo 4: Coleções de Conteúdo
Coleções de conteúdo fornecem acesso com segurança de tipos a arquivos Markdown e MDX:
// src/content/config.ts
import { z, defineCollection } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
date: z.coerce.date(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
});
export const collections = { blog };
---
// src/pages/blog/index.astro
import { getCollection } from 'astro:content';
const posts = (await getCollection('blog'))
.filter(p => !p.data.draft)
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
---
<ul>
{posts.map(post => (
<li>
<a href={`/blog/${post.slug}`}>{post.data.title}</a>
<time>{post.data.date.toLocaleDateString()}</time>
</li>
))}
</ul>
Passo 5: Ilhas — Hidratação Seletiva
Por padrão, componentes de frameworks de UI renderizam para HTML estático sem JS. Use diretivas client: para hidratar:
---
import Counter from '../components/Counter.tsx'; // Componente React
import VideoPlayer from '../components/VideoPlayer.svelte';
---
<!-- HTML estático — nenhum JavaScript enviado ao navegador -->
<Counter initialCount={0} />
<!-- Hidrata imediatamente no carregamento da página -->
<Counter initialCount={0} client:load />
<!-- Hidrata quando o componente entra na viewport -->
<VideoPlayer src="/demo.mp4" client:visible />
<!-- Hidrata apenas quando o navegador está ocioso -->
<Analytics client:idle />
<!-- Hidrata apenas em uma media query específica -->
<MobileMenu client:media="(max-width: 768px)" />
Passo 6: Layouts
---
// src/layouts/BaseLayout.astro
interface Props {
title: string;
description?: string;
}
const { title, description = 'Meu Site Astro' } = Astro.props;
---
<html lang="pt-BR">
<head>
<meta charset="utf-8" />
<title>{title}</title>
<meta name="description" content={description} />
</head>
<body>
<nav>...</nav>
<main>
<slot /> <!-- o conteúdo da página é renderizado aqui -->
</main>
<footer>...</footer>
</body>
</html>
---
// src/pages/about.astro
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout title="Sobre Nós">
<h1>Sobre Nós</h1>
<p>Bem-vindo à nossa empresa...</p>
</BaseLayout>
Passo 7: Modo SSR (Renderização Sob Demanda)
Ative SSR para páginas dinâmicas definindo um adaptador:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/serverless';
export default defineConfig({
output: 'hybrid', // 'static' | 'server' | 'hybrid'
adapter: vercel(),
});
Opte páginas individuais para SSR com export const prerender = false.
Exemplos
Exemplo 1: Blog com Feed RSS
// src/pages/rss.xml.ts
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
export async function GET(context) {
const posts = await getCollection('blog');
return rss({
title: 'Meu Blog',
description: 'Postagens recentes',
site: context.site,
items: posts.map(post => ({
title: post.data.title,
pubDate: post.data.date,
link: `/blog/${post.slug}/`,
})),
});
}
Exemplo 2: Endpoint de API (SSR)
// src/pages/api/subscribe.ts
import type { APIRoute } from 'astro';
export const POST: APIRoute = async ({ request }) => {
const { email } = await request.json();
if (!email) {
return new Response(JSON.stringify({ error: 'Email obrigatório' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
await addToNewsletter(email);
return new Response(JSON.stringify({ success: true }), { status: 200 });
};
Exemplo 3: Componente React como Ilha
// src/components/SearchBox.tsx
import { useState } from 'react';
export default function SearchBox() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
async function search(e: React.FormEvent) {
e.preventDefault();
const data = await fetch(`/api/search?q=${query}`).then(r => r.json());
setResults(data);
}
return (
<form onSubmit={search}>
<input value={query} onChange={e => setQuery(e.target.value)} />
<button type="submit">Pesquisar</button>
<ul>{results.map(r => <li key={r.id}>{r.title}</li>)}</ul>
</form>
);
}
---
import SearchBox from '../components/SearchBox.tsx';
---
<!-- Hidratado imediatamente — esta ilha é interativa -->
<SearchBox client:load />
Melhores Práticas
- ✅ Mantenha a maioria dos componentes como arquivos
.astroestáticos — hidrate apenas o que precisa ser interativo - ✅ Use coleções de conteúdo para todo conteúdo Markdown/MDX — você ganha segurança de tipos e validação automática
- ✅ Prefira
client:visibleem vez declient:loadpara componentes abaixo da dobra para reduzir o JS inicial - ✅ Use
import.meta.envpara variáveis de ambiente — prefixe variáveis públicas comPUBLIC_ - ✅ Adicione
<ViewTransitions />deastro:transitionspara navegação suave entre páginas sem um SPA completo - ❌ Não use
client:loadem todos os componentes — isso anula a vantagem de desempenho do Astro - ❌ Não coloque segredos no frontmatter de arquivos
.astroque sejam usados em templates expostos ao cliente - ❌ Não pule
getStaticPathspara rotas dinâmicas no modo estático — as builds falharão
Notas de Segurança e Proteção
- O código no frontmatter de arquivos
.astroé executado apenas no servidor e nunca é exposto ao navegador. - Use
import.meta.env.PUBLIC_*apenas para valores não sensíveis. Variáveis de ambiente privadas (sem prefixoPUBLIC_) nunca são enviadas ao cliente. - Ao usar modo SSR, valide todas as entradas de
Astro.requestantes de consultas ao banco de dados ou chamadas de API. - Sanitize qualquer conteúdo fornecido pelo usuário antes de renderizar com
set:html— ele ignora o escape automático.
Problemas Comuns
-
Problema: JavaScript de um componente React/Vue não é executado no navegador Solução: Adicione uma diretiva
client:(client:load,client:visible, etc.) — sem ela, os componentes renderizam apenas como HTML estático. -
Problema: Dados de
getStaticPathsestão desatualizados após atualizações de conteúdo durante o desenvolvimento Solução: O servidor de desenvolvimento do Astro monitora arquivos de conteúdo — reinicie se as alterações emcontent/config.tsnão forem refletidas. -
Problema: O tipo de
Astro.propséany— sem autocomplete Solução: Defina uma interface ou tipoPropsno frontmatter e o Astro a inferirá automaticamente. -
Problema: O CSS de um componente
.astrovaza para outros componentes Solução: Estilos em tags<style>de arquivos.astrosão automaticamente escopados. Use:global()apenas quando intencionalmente direcionar filhos.
Skills Relacionadas
@sveltekit— Quando você precisa de um framework full-stack com UI reativa (em contraste com o foco em conteúdo do Astro)@nextjs-app-router-patterns— Quando você precisa de um framework full-stack focado em React@tailwind-patterns— Estilizando sites Astro com Tailwind CSS@progressive-web-app— Adicionando capacidades PWA a um site Astro
Limitações
- Use esta skill apenas quando a tarefa corresponder claramente ao escopo descrito acima.
- Não trate a saída como substituta para validação específica de ambiente, testes ou revisão especializada.
- Pare e peça esclarecimentos se entradas, permissões, limites de segurança ou critérios de sucesso necessários estiverem faltando.