Pular para o conteúdo

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 scripts

Explicaçã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

  1. 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
  2. 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
  3. Componentes de Layout (src/components/layout/)

    • Componentes estruturais que definem a organização da página
    • Exemplos: Header.astro, Footer.astro, Sidebar.astro
  4. 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: *List para listas, *Item para itens de lista, *Container para 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.astro

Estraté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

  1. Responsabilidade Única

    • Cada componente deve ter uma única responsabilidade
    • Se um componente faz muitas coisas, divida-o em componentes menores
  2. Composição

    • Crie componentes complexos compondo componentes mais simples
    • Use slots do Astro para injetar conteúdo em componentes
  3. Reutilização

    • Identifique padrões repetidos e extraia-os em componentes
    • Parametrize componentes com props para flexibilidade
  4. Isolamento

    • Componentes devem funcionar de forma independente
    • Minimize dependências entre componentes

Exemplo de Modularização

src/components/ui/Button.astro
---
// Componente base de botão
const { 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>
src/components/common/CallToAction.astro
---
// Componente que utiliza o botão em um contexto específico
import 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

  1. Content Collections

    • Conteúdo estruturado com validação de esquema
    • Ideal para blogs, documentação, produtos, etc.
  2. APIs Externas

    • Dados de serviços externos via fetch
    • Pode ser chamado no build time (SSG) ou em runtime (SSR)
  3. Parâmetros de URL

    • Dados passados via parâmetros de rota ou query string
    • Acessíveis via Astro.params e Astro.url.searchParams
  4. 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:

src/pages/products/[id].astro
---
import ProductLayout from '../../layouts/ProductLayout.astro';
import ProductDetails from '../../components/product/ProductDetails.astro';
import RelatedProducts from '../../components/product/RelatedProducts.astro';
// Obter dados do produto
const { 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:

src/components/product/ProductReviews.astro
---
// Componente que busca seus próprios dados
const { productId } = Astro.props;
// Fetch de dados específicos para este componente
const 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:

src/components/common/TabGroup.astro
---
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

  1. Abstração de Serviços

Crie módulos de serviço para encapsular a lógica de API:

src/lib/api/products.js
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();
}
  1. Endpoints de API no Servidor

Crie endpoints de API para operações do servidor:

src/pages/api/newsletter.js
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:

  1. Lazy Loading de Componentes

    • Carregue componentes pesados apenas quando necessário
    • Use client:visible para hidratação baseada em visibilidade
  2. Microfrontends

    • Divida aplicações grandes em partes menores e independentes
    • Use subprojetos Astro ou integração com outras tecnologias
  3. Monorepo

    • Para projetos muito grandes, considere uma estrutura de monorepo
    • Compartilhe componentes e utilitários entre projetos relacionados
  4. 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:


Última atualização: 21 de março de 2025