Domine a Arquitetura do Vue.js: Guia Definitivo para Componentes, Rotas e Boas Práticas

Domine a Arquitetura do Vue.js: Guia Definitivo para Componentes, Rotas e Boas Práticas

“`html





Dominando a Arquitetura de Vue.js — Guia Técnico Completo

Arquitetura Vue.js • do projeto ao código

Dominando a Arquitetura de Vue.js

Eu organizo Vue para escalar com clareza: responsabilidades bem separadas, previsibilidade de estado,
padrões consistentes de componentes e uma base de projeto que facilita manutenção.

Estrutura de pastas
Fluxo de dados
Componentes reaproveitáveis
Boas práticas de estado

01

Comece pela estrutura: pastas que “explicam” o projeto

A primeira decisão arquitetural é a estrutura de pastas. Ela serve como documentação viva:
quando você abre o repositório, já precisa entender onde fica o quê.

Uma estrutura consistente reduz acoplamento e evita o “espalhamento” de lógica em arquivos aleatórios.

Organize por camadas

ui (componentes), features (casos de uso), services (integrações), store (estado) e shared (utilidades).

Nomeie por intenção

prefira “user-profile”, “orders-list” e “auth-session” a “page1”, “componentA”, “utils2”.

Evite lógica em componentes

componentes devem orquestrar interação e apresentação; regras de domínio vão para módulos.

Compartilhe com critério

utilitários genéricos ficam em shared; peças específicas de uma feature ficam naquela feature.

Sugestão prática de organização para projetos Vue (com Vite ou similar):

// src/
src/
  app/
    App.vue
    routes.ts
  ui/
    components/
      BaseButton.vue
      BaseInput.vue
      Modal.vue
    layouts/
      AppLayout.vue
  features/
    auth/
      components/
        LoginForm.vue
      composables/
        useAuth.ts
      api/
        authApi.ts
      types.ts
    dashboard/
      components/
        StatsPanel.vue
      api/
        dashboardApi.ts
      composables/
        useDashboard.ts
  store/
    index.ts
    authStore.ts
  shared/
    composables/
      useDebounce.ts
    utils/
      format.ts
    constants/
      http.ts

02

Fluxo de dados: do endpoint até a tela sem bagunça

Arquitetura é previsibilidade. Eu gosto de garantir um fluxo de dados “em linha”:
API → serviço/composable → estado → componente.

Na prática, eu separo:

  • Camada de integração: functions que falam com HTTP (sem depender de componentes).
  • Camada de orquestração: composables que combinam efeitos, validações e transformação de dados.
  • Camada de estado: store (ou estado local) com regras de consistência.
  • Camada de apresentação: componentes focados em UI e eventos do usuário.

Dica importante: reduza “efeitos colaterais” espalhados. Ao invés de buscar dados dentro de múltiplos componentes,
centralize a busca na composable ou no store da feature.

// features/auth/api/authApi.ts
export type LoginPayload = { email: string; password: string };

export async function loginApi(payload: LoginPayload) {
  const res = await fetch("/api/login", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(payload),
  });

  if (!res.ok) {
    const data = await res.json().catch(() => ({}));
    throw new Error(data?.message || "Falha ao realizar login");
  }

  return res.json() as Promise<{ token: string; user: { id: string; name: string } }>;
}

03

Componentes com responsabilidade única (e slots quando faz sentido)

Um componente bom é previsível, reutilizável e fácil de testar. Eu divido a responsabilidade em três tipos:

Apresentação

recebe props e emite eventos; sem regra complexa de domínio.

Orquestração de feature

usa composable/store da feature e monta a UI final (quando necessário).

UI base

botões, inputs, modais e componentes genéricos reutilizáveis.

Layout

estrutura geral da aplicação (header, sidebar, container), sem lógica de negócio.

Sobre slots: quando você percebe que precisa “trocar pedaços” do layout do componente
(ex.: cabeçalho, ações, conteúdo), use slots para manter o componente flexível sem criar variações duplicadas.

<!-- ui/components/Modal.vue -->
<template>
  <div v-if="open" class="overlay" @click.self="$emit('close')">
    <div class="modal" role="dialog" aria-modal="true">
      <header class="modal__header">
        <slot name="title"> </slot>
        <button class="x" type="button" @click="$emit('close')" aria-label="Fechar">✕</button>
      </header>

      <section class="modal__body">
        <slot />
      </section>

      <footer class="modal__footer">
        <slot name="actions"> </slot>
      </footer>
    </div>
  </div>
</template>

04

Estado e reatividade: onde colocar e como evitar inconsistências

O maior risco arquitetural em Vue é tratar estado de forma “orgânica”: um pedaço aqui, outro ali,
e lógica espalhada entre eventos e lifecycle. Para evitar isso, eu sigo critérios objetivos.

Regra 1 — estado compartilhado fica na store

Se duas telas/partes dependem do mesmo dado (ex.: sessão, permissões, filtros persistentes), ele merece store.

Regra 2 — estado de UI local fica no componente

Modal aberto, formulário com valores temporários e toggles de interface podem ficar local,
desde que não virem “fonte de verdade” de domínio.

Regra 3 — derive ao invés de replicar

Sempre que possível, use computed/derivações para evitar duplicar lógica (ex.: “é válido” a partir de campos).

Exemplo completo (uma composable de feature que controla loading/erro, chama a API e expõe uma interface simples).

// features/auth/composables/useAuth.ts
import { ref, computed } from "vue";
import { loginApi, type LoginPayload } from "../api/authApi";

type AuthState = {
  loading: boolean;
  error: string | null;
  token: string | null;
};

export function useAuth() {
  const state = ref<AuthState>({
    loading: false,
    error: null,
    token: null,
  });

  const isAuthenticated = computed(() => !!state.value.token);

  async function login(payload: LoginPayload) {
    state.value.loading = true;
    state.value.error = null;

    try {
      const data = await loginApi(payload);
      state.value.token = data.token;
    } catch (err) {
      state.value.error = err instanceof Error ? err.message : "Erro inesperado";
      state.value.token = null;
    } finally {
      state.value.loading = false;
    }
  }

  function logout() {
    state.value = { loading: false, error: null, token: null };
  }

  return {
    state,
    isAuthenticated,
    login,
    logout,
  };
}

Quer evoluir ainda mais sua arquitetura?

Continue no yurideveloper.com com posts que aprofundam Vue na prática: padrões de componentes,
organização de features, rotas e estratégias para estado.



“`