2. Arquitetura
Este documento detalha a arquitetura recomendada para soluções web desenvolvidas com Astro 5, Tailwind CSS e JavaScript vanilla, fornecendo uma estrutura sólida para projetos escaláveis e de fácil manutenção.
Estrutura de Pastas Recomendada
A organização de arquivos e pastas é fundamental para a manutenção e escalabilidade do projeto. Abaixo está a estrutura recomendada:
projeto/├── public/ # Arquivos estáticos servidos diretamente│ ├── favicon.svg│ ├── robots.txt│ ├── fonts/│ └── images/├── src/│ ├── assets/ # Assets processados pelo build│ │ ├── images/│ │ └── styles/│ ├── components/ # Componentes reutilizáveis│ │ ├── common/ # Componentes de uso geral│ │ ├── layout/ # Componentes de estrutura│ │ └── ui/ # Componentes de interface│ ├── content/ # Content Collections│ │ ├── blog/│ │ ├── products/│ │ └── config.ts # Configuração das collections│ ├── layouts/ # Layouts de página│ │ ├── BaseLayout.astro│ │ └── PostLayout.astro│ ├── lib/ # Utilitários e funções auxiliares│ │ ├── api.js│ │ ├── helpers.js│ │ └── constants.js│ ├── pages/ # Rotas e páginas│ │ ├── index.astro│ │ ├── about.astro│ │ ├── blog/│ │ │ ├── index.astro│ │ │ └── [...slug].astro│ │ └── api/ # Endpoints de API (SSR)│ └── utils/ # Funções utilitárias│ ├── date.js│ └── string.js├── astro.config.mjs # Configuração do Astro├── tailwind.config.js # Configuração do Tailwind├── tsconfig.json # Configuração do TypeScript└── package.json # Dependências e scriptsExplicação das Pastas Principais
- public/: Arquivos estáticos que são copiados diretamente para a pasta de build sem processamento.
- src/assets/: Recursos que passam pelo processo de build (otimização, transformação).
- src/components/: Componentes reutilizáveis organizados por função.
- src/content/: Content Collections para gerenciar conteúdo estruturado com validação de esquema.
- src/layouts/: Estruturas de página reutilizáveis.
- src/lib/: Código compartilhado específico do projeto.
- src/pages/: Define as rotas da aplicação baseadas no sistema de arquivos.
- src/utils/: Funções utilitárias genéricas.
Organização de Componentes
Os componentes são organizados seguindo uma estrutura hierárquica que facilita a localização e reutilização:
Categorias de Componentes
-
Componentes de UI (src/components/ui/)
- Elementos básicos de interface (botões, inputs, cards)
- Independentes de contexto e altamente reutilizáveis
- Exemplos:
Button.astro,Card.astro,Input.astro
-
Componentes Comuns (src/components/common/)
- Componentes de uso geral que podem aparecer em múltiplas páginas
- Podem combinar vários componentes UI
- Exemplos:
SearchBar.astro,Pagination.astro,Alert.astro
-
Componentes de Layout (src/components/layout/)
- Componentes estruturais que definem a organização da página
- Exemplos:
Header.astro,Footer.astro,Sidebar.astro
-
Componentes de Página (src/pages/)
- Componentes específicos para uma página ou rota
- Combinam componentes de outras categorias
- Exemplos:
HomePage.astro,ProductDetail.astro
Convenções de Nomenclatura
- Use PascalCase para nomes de componentes:
ProductCard.astro - Use nomes descritivos que indicam a função do componente
- Prefixe componentes relacionados:
FormInput.astro,FormSelect.astro,FormCheckbox.astro - Sufixos comuns:
*Listpara listas,*Itempara itens de lista,*Containerpara wrappers
src/components/├── ui/│ ├── Button.astro│ ├── Card.astro│ ├── Icon.astro│ └── form/│ ├── Input.astro│ ├── Select.astro│ └── Checkbox.astro├── common/│ ├── Breadcrumbs.astro│ ├── Pagination.astro│ └── SearchBar.astro└── layout/ ├── Header.astro ├── Footer.astro ├── Sidebar.astro └── navigation/ ├── MainNav.astro └── MobileMenu.astroEstratégia de Modularização
A modularização eficiente é essencial para manter o código organizado e facilitar a manutenção.
Princípios de Modularização
-
Responsabilidade Única
- Cada componente deve ter uma única responsabilidade
- Se um componente faz muitas coisas, divida-o em componentes menores
-
Composição
- Crie componentes complexos compondo componentes mais simples
- Use slots do Astro para injetar conteúdo em componentes
-
Reutilização
- Identifique padrões repetidos e extraia-os em componentes
- Parametrize componentes com props para flexibilidade
-
Isolamento
- Componentes devem funcionar de forma independente
- Minimize dependências entre componentes
Exemplo de Modularização
---// Componente base de botãoconst { variant = 'primary', size = 'md', class: className, ...props } = Astro.props;
const variantClasses = { primary: 'bg-blue-600 text-white hover:bg-blue-700', secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300', outline: 'bg-transparent border border-current text-blue-600 hover:bg-blue-50'};
const sizeClasses = { sm: 'text-sm px-2 py-1', md: 'px-4 py-2', lg: 'text-lg px-6 py-3'};
const classes = `rounded font-medium transition-colors ${variantClasses[variant]} ${sizeClasses[size]} ${className || ''}`;---
<button class={classes} {...props}> <slot /></button>---// Componente que utiliza o botão em um contexto específicoimport Button from '../ui/Button.astro';
const { title, description, buttonText, buttonUrl } = Astro.props;---
<div class="bg-gray-100 p-6 rounded-lg text-center"> <h2 class="text-2xl font-bold mb-2">{title}</h2> <p class="text-gray-600 mb-4">{description}</p> <Button href={buttonUrl} variant="primary" size="lg"> {buttonText} </Button></div>Fluxo de Dados
O fluxo de dados em uma aplicação Astro segue padrões claros que facilitam o rastreamento e a manutenção.
Fontes de Dados
-
Content Collections
- Conteúdo estruturado com validação de esquema
- Ideal para blogs, documentação, produtos, etc.
-
APIs Externas
- Dados de serviços externos via fetch
- Pode ser chamado no build time (SSG) ou em runtime (SSR)
-
Parâmetros de URL
- Dados passados via parâmetros de rota ou query string
- Acessíveis via
Astro.paramseAstro.url.searchParams
-
Formulários
- Dados submetidos pelo usuário
- Processados via endpoints de API em
src/pages/api/
Padrões de Fluxo de Dados
1. Top-down via Props
O padrão mais comum é passar dados de cima para baixo através de props:
---import ProductLayout from '../../layouts/ProductLayout.astro';import ProductDetails from '../../components/product/ProductDetails.astro';import RelatedProducts from '../../components/product/RelatedProducts.astro';
// Obter dados do produtoconst { id } = Astro.params;const product = await getProductById(id);const relatedProducts = await getRelatedProducts(product.category);---
<ProductLayout title={product.name}> <ProductDetails product={product} /> <RelatedProducts products={relatedProducts} /></ProductLayout>2. Componentes Autônomos
Componentes podem buscar seus próprios dados:
---// Componente que busca seus próprios dadosconst { productId } = Astro.props;
// Fetch de dados específicos para este componenteconst reviews = await getProductReviews(productId);---
<section class="mt-8"> <h2 class="text-xl font-bold mb-4">Avaliações de Clientes</h2> {reviews.length > 0 ? ( <ul class="space-y-4"> {reviews.map(review => ( <li class="border p-4 rounded"> <div class="flex items-center mb-2"> <span class="font-medium">{review.author}</span> <div class="ml-2"> {/* Componente de estrelas */} <StarRating rating={review.rating} /> </div> </div> <p>{review.content}</p> </li> ))} </ul> ) : ( <p>Ainda não há avaliações para este produto.</p> )}</section>3. Gerenciamento de Estado no Cliente
Para componentes interativos, o estado é gerenciado no cliente com JavaScript:
---const { tabs } = Astro.props;---
<div class="tab-group"> <div class="tab-header flex border-b"> {tabs.map((tab, index) => ( <button class="px-4 py-2 tab-button" data-index={index} aria-selected={index === 0 ? 'true' : 'false'} > {tab.label} </button> ))} </div>
<div class="tab-content py-4"> {tabs.map((tab, index) => ( <div class="tab-panel" data-index={index} hidden={index !== 0} > <slot name={`tab-${index}`} /> </div> ))} </div></div>
<script> // Estado gerenciado no cliente function setupTabs() { const tabGroups = document.querySelectorAll('.tab-group');
tabGroups.forEach(group => { const buttons = group.querySelectorAll('.tab-button'); const panels = group.querySelectorAll('.tab-panel');
buttons.forEach(button => { button.addEventListener('click', () => { const targetIndex = button.dataset.index;
// Atualizar botões buttons.forEach(btn => { btn.setAttribute('aria-selected', btn.dataset.index === targetIndex ? 'true' : 'false'); });
// Atualizar painéis panels.forEach(panel => { panel.hidden = panel.dataset.index !== targetIndex; }); }); }); }); }
// Executar quando o DOM estiver pronto document.addEventListener('DOMContentLoaded', setupTabs);
// Suporte para navegação por Astro View Transitions document.addEventListener('astro:page-load', setupTabs);</script>
<style> .tab-button[aria-selected="true"] { border-bottom: 2px solid currentColor; font-weight: bold; }</style>Integração de APIs e Serviços
Padrões para Integração de APIs
- Abstração de Serviços
Crie módulos de serviço para encapsular a lógica de API:
const API_URL = import.meta.env.PUBLIC_API_URL;
export async function getProducts(category = null) { const url = new URL(`${API_URL}/products`); if (category) { url.searchParams.append('category', category); }
const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to fetch products: ${response.statusText}`); }
return response.json();}
export async function getProductById(id) { const response = await fetch(`${API_URL}/products/${id}`); if (!response.ok) { throw new Error(`Failed to fetch product: ${response.statusText}`); }
return response.json();}- Endpoints de API no Servidor
Crie endpoints de API para operações do servidor:
export async function POST({ request }) { const formData = await request.formData(); const email = formData.get('email');
// Validação if (!email || !email.includes('@')) { return new Response( JSON.stringify({ success: false, message: 'Email inválido' }), { status: 400, headers: { 'Content-Type': 'application/json' } } ); }
try { // Integração com serviço de newsletter await subscribeToNewsletter(email);
return new Response( JSON.stringify({ success: true, message: 'Inscrição realizada com sucesso!' }), { status: 200, headers: { 'Content-Type': 'application/json' } } ); } catch (error) { console.error('Newsletter subscription error:', error);
return new Response( JSON.stringify({ success: false, message: 'Erro ao processar inscrição' }), { status: 500, headers: { 'Content-Type': 'application/json' } } ); }}
async function subscribeToNewsletter(email) { // Implementação da integração com o serviço // ...}Considerações para Escalabilidade
À medida que o projeto cresce, considere estas estratégias:
-
Lazy Loading de Componentes
- Carregue componentes pesados apenas quando necessário
- Use
client:visiblepara hidratação baseada em visibilidade
-
Microfrontends
- Divida aplicações grandes em partes menores e independentes
- Use subprojetos Astro ou integração com outras tecnologias
-
Monorepo
- Para projetos muito grandes, considere uma estrutura de monorepo
- Compartilhe componentes e utilitários entre projetos relacionados
-
Caching
- Implemente estratégias de cache para dados frequentemente acessados
- Use serviços como Cloudflare ou Vercel para edge caching
Conclusão
A arquitetura descrita neste documento fornece uma base sólida para desenvolver soluções web modernas com Astro 5. Seguindo estas diretrizes de organização, modularização e fluxo de dados, você criará aplicações mais fáceis de manter, estender e escalar.
Os próximos documentos detalharão aspectos específicos desta arquitetura:
- Style Guide: Convenções de código e formatação
- Design System: Sistema visual e componentes base
- Componentes: Biblioteca de componentes reutilizáveis
Última atualização: 21 de março de 2025