Arquitetura frontend vai além de organizar componentes cover image

Arquitetura frontend vai além de organizar componentes

No começo de um projeto, “arquitetura frontend” vira debate de pastas. Componentes ficam em components/ ou em features/? Separamos por domínio ou por camada? Isso importa, mas ainda é estante de arquivos — não é arquitetura. Arquitetura aparece quando o app cresce: dois times mexem no mesmo módulo, um bug no checkout quebra o dashboard e uma mudança “pequena” na API vira uma semana de refactor.

Se você trabalha com Nuxt, Vue e TypeScript, já tem peças sólidas. O ponto é conectá-las para o sistema continuar legível quando o produto muda. Boa arquitetura faz o caminho padrão ser o caminho seguro: a forma como alguém novo implementa uma feature deve respeitar limites, tipagem e fluxo de dados quase sem pensar.

Arquitetura são restrições que você escolhe de propósito

Todo frontend decide coisas em silêncio: onde fica o estado do servidor, se componente busca dado direto, como erro sobe, como rota mapeia permissão. Arquitetura é tornar isso explícito e, quando possível, verificável.

Pense em capacidades, não em arquivos:

  • Dono dos dados — Quem pode alterar um estado e por qual API?
  • Composição — Como pedaços de UI se montam sem conhecer detalhes internos uns dos outros?
  • Isolamento de mudança — Dá para trocar cliente HTTP ou refazer uma tela sem efeito dominó?
  • Observabilidade — Quando falha em produção, dá para rastrear da ação na UI até a chamada de rede?

Árvore de componentes organizada ajuda a ler código; restrições ajudam o sistema envelhecer bem. Sem regras, pastas “shared” viram gaveta de bagunça e cada tela inventa seu jeito de buscar dado.

Camadas vencem endereço de pasta

Pasta por tipo (components, composables, stores) escala até features cortarem tudo. Pasta por feature escala até duplicar primitivos. Codebases maduras misturam: módulos de feature no centro e um núcleo compartilhado fino — tokens, HTTP, auth, helpers de rota.

Modelo de camadas que funciona bem em apps Vue/Nuxt:

  • Camada de UI — Componentes presentacionais, layout, acessibilidade. Pouca lógica; props entram, eventos saem.
  • Camada de aplicação — Casos de uso: “finalizar checkout”, “convidar colega”. Orquestra stores, composables e navegação.
  • Camada de domínio — Tipos, regras de validação, funções puras (totais, elegibilidade). Evite importar Vue aqui.
  • Camada de infraestrutura — Clientes HTTP, WebSocket, localStorage, SDKs de terceiros.

No Nuxt 3, coloque lógica específica de rota em pages/ ou grupos de rotas, mas tire regras pesadas do script da page. A page deve parecer índice: conecta um caso de uso e renderiza. Quando passar disso, extraia um serviço de aplicação — muitas vezes um composable com nome claro, como useCheckoutFlow.

Estado: nem tudo precisa ir para o Pinia

Store global é prático e perigoso. Arquitetura define o que precisa ser global e o que morre com a rota.

  • Estado do servidor — Dados de API com cache, staleness e invalidação. Trate diferente de toggle de UI.
  • Estado de sessão — Identidade, locale, tema. Superfície pequena, acesso controlado.
  • Estado efêmero de UI — Modais, passo de wizard, rascunho de formulário. Prefira estado local ou composable com escopo.
  • Estado derivado — Calculado do resto; nunca duplique na mão.

Quando todo componente chama useFetch ou o Pinia para a mesma entidade, loading fica inconsistente e aparecem race conditions. Centralize política de fetch por recurso — em geral um composable sobre cliente tipado — para a UI pedir intenção (“carregar pedido 123”) e não transporte (“GET /orders/123”).

Com backend Laravel, alinhe módulos frontend aos limites da API: um módulo por agregado exposto (pedidos, assinaturas, organizações). Tipos TypeScript espelham DTOs, com mappers na borda de infraestrutura para o domínio não parsear JSON cru.

Limites que aparecem nos tipos

TypeScript vale quando codifica arquitetura. Use tipos para bloquear atalhos:

  • Exporte interfaces públicas estreitas por feature; componentes internos ficam privados.
  • Modele async como Result ou uniões discriminadas (idle | loading | success | error) em vez de vários booleanos.
  • Marque IDs (OrderId, UserId) para não cruzar entidades por engano em bases grandes.

Validação em runtime na borda ainda é essencial — zod ou similar no HTTP pega drift de contrato antes de contaminar a UI. Arquitetura quebra em silêncio quando o backend muda um campo e o TypeScript compila porque a borda virou any.

Rotas, code splitting e modos de falha

No Nuxt, rotas são arquitetura. Definem entradas, cadeia de middleware e chunks lazy. Decida cedo:

  • Quais rotas exigem auth e como redirect preserva intenção (URL de retorno).
  • Onde layout aninhado para para não esconder tratamento global de erro.
  • Como mostrar falha parcial (skeleton, error boundary, retry).

Middleware não é só guarda — é política. Mantenha fino; regras vão para funções testáveis. Em áreas por papel, combine meta da rota com um helper authorize único para regra de produto e segurança não espalharem em dezenas de arquivos.

Preocupações transversais sem espaguete

Analytics, feature flag, i18n e toast aparecem em todo lugar. Sem padrão, cada feature importa helper de um jeito. Escolha um mecanismo:

  • Plugins para singletons globais (analytics, logger).
  • Composables para comportamento reutilizado (useToast, useTrack).
  • Diretivas ou componentes só quando o abstração é comportamento de DOM.

Registre a decisão num ADR curto. Ninguém deveria vasculhar o repo inteiro para descobrir como registrar evento.

Testes e evolução

Arquitetura se prova na mudança. Funções puras de domínio ganham teste unitário. Composables de aplicação testam com infra mockada. UI leva teste de componente nos fluxos críticos e um fio fino de E2E nos caminhos de receita.

Faça auditoria de dependências de tempos em tempos: quem importa quem? Gráficos de import revelam ciclos cedo. Ciclo entre billing e settings costuma anteceder incidente em produção.

Checklist antes de renomear pasta de novo

  • Um time de feature consegue dono de fatia vertical (UI + estado + adapter de API) com pouco conflito de merge?
  • Estado do servidor passa por uma política por recurso, com erros tipados?
  • Pages continuam finas, com casos de uso nomeados na linguagem do negócio?
  • Comportamentos transversais (auth, tracking, i18n) têm um jeito único de acesso?
  • Dá para explicar fluxo do clique até a API num diagrama, sem mão na massa?

Organizar componentes é faxina. Arquitetura é desenhar como o frontend pensa: o que sabe, o que recusa saber e como se recupera quando a rede ou o produto muda de ideia. Acertando essas decisões, a estrutura de pastas quase se arruma sozinha.