💚 Vue.js Stack

Frontend Stacks

IA Guidelines / Frontend Stacks / Vue.js Stack
⬇️ Download .md

Stack Vue.js — InfoWhere

Template de stack para projetos Vue.js
Última atualização: 25/01/2026
Uso: Principal (projetos pessoais InfoWhere)


1. Visão Geral

Stack frontend principal para projetos InfoWhere e side projects.

Filosofia

  • Bootstrap como UI framework (familiar, produtivo)
  • Componentes pragmáticos — dividir quando faz sentido, não por dogma
  • Composition API com ref(), computed(), watch()
  • Pinia para estado global
  • Diretivas quando simplificam o código

2. Versões

Componente Versão Notas
Vue 3.5.x Composition API
TypeScript 5.x Sempre usar TypeScript
Vite 6.x Build tool
pnpm Última Gerenciador de pacotes

3. Dependências Core

3.1 Framework Base

{
  "dependencies": {
    "vue": "^3.5.0",
    "vue-router": "^4.4.0",
    "pinia": "^2.2.0"
  },
  "devDependencies": {
    "typescript": "^5.6.0",
    "vite": "^6.0.0",
    "@vitejs/plugin-vue": "^5.2.0",
    "vue-tsc": "^2.1.0"
  }
}

3.2 UI Framework

{
  "dependencies": {
    "bootstrap": "^5.3.0",
    "@popperjs/core": "^2.11.0"
  }
}

Nota: Bootstrap é o padrão. PrimeVue só se precisar de componentes complexos (data tables, calendários, etc.).


### 3.3 HTTP Client

```json
{
  "dependencies": {
    "axios": "^1.7.0"
  }
}

3.4 Autenticação

{
  "dependencies": {
    "keycloak-js": "^26.0.0"
  }
}

3.5 Utilitários

{
  "dependencies": {
    "vee-validate": "^4.14.0",
    "yup": "^1.4.0",
    "@vueuse/core": "^11.3.0",
    "date-fns": "^4.1.0"
  }
}

4. Testes

{
  "devDependencies": {
    "vitest": "^2.1.0",
    "@vue/test-utils": "^2.4.0",
    "@testing-library/vue": "^8.1.0",
    "jsdom": "^25.0.0",
    "@vitest/coverage-v8": "^2.1.0"
  }
}

Cobertura mínima: 70%


5. Autenticação

Componente Escolha
Identity Provider Keycloak
Lib keycloak-js
Storage Memory (não localStorage)

Configuração típica

// src/config/keycloak.ts
import Keycloak from 'keycloak-js';

export const keycloak = new Keycloak({
  url: import.meta.env.VITE_KEYCLOAK_URL,
  realm: import.meta.env.VITE_KEYCLOAK_REALM,
  clientId: import.meta.env.VITE_KEYCLOAK_CLIENT_ID,
});

export async function initKeycloak(): Promise<boolean> {
  try {
    const authenticated = await keycloak.init({
      onLoad: 'check-sso',
      silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`,
      pkceMethod: 'S256',
    });
    return authenticated;
  } catch (error) {
    console.error('Keycloak init failed', error);
    return false;
  }
}

Axios Interceptor

// src/config/axios.ts
import axios from 'axios';
import { keycloak } from './keycloak';

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL,
});

api.interceptors.request.use(async (config) => {
  if (keycloak.token) {
    // Refresh token if expiring in 30 seconds
    if (keycloak.isTokenExpired(30)) {
      await keycloak.updateToken(30);
    }
    config.headers.Authorization = `Bearer ${keycloak.token}`;
  }
  return config;
});

export default api;

6. Estrutura do Projeto

{projeto}/
├── public/
│   ├── favicon.ico
│   └── silent-check-sso.html    # Keycloak SSO
├── src/
│   ├── main.ts                  # Entry point
│   ├── App.vue
│   ├── assets/
│   │   └── styles/
│   │       ├── main.scss
│   │       └── _variables.scss
│   ├── components/
│   │   ├── common/              # Reusable components
│   │   │   ├── AppHeader.vue
│   │   │   ├── AppFooter.vue
│   │   │   └── AppLoading.vue
│   │   └── features/            # Feature-specific
│   │       └── users/
│   │           └── UserCard.vue
│   ├── composables/             # Composition functions
│   │   ├── useAuth.ts
│   │   └── useApi.ts
│   ├── config/
│   │   ├── keycloak.ts
│   │   └── axios.ts
│   ├── layouts/
│   │   ├── DefaultLayout.vue
│   │   └── AuthLayout.vue
│   ├── pages/                   # Route pages
│   │   ├── HomePage.vue
│   │   ├── LoginPage.vue
│   │   └── users/
│   │       ├── UserListPage.vue
│   │       └── UserDetailPage.vue
│   ├── router/
│   │   ├── index.ts
│   │   └── guards.ts
│   ├── stores/                  # Pinia stores
│   │   ├── index.ts
│   │   ├── auth.store.ts
│   │   └── user.store.ts
│   ├── services/                # API services
│   │   └── user.service.ts
│   ├── types/
│   │   └── index.ts
│   └── utils/
│       └── formatters.ts
├── tests/
│   ├── setup.ts
│   ├── unit/
│   └── components/
├── index.html
├── package.json
├── tsconfig.json
├── vite.config.ts
├── .env.example
└── README.md

7. Padrões e Convenções

7.1 Componentes — Abordagem Pragmática

Princípio: Dividir quando faz sentido, não por dogma.

Quando criar componente separado Quando NÃO separar
Reutilizado em 2+ lugares Usado só uma vez e simples
Lógica complexa isolada Poucos elementos HTML
Testável independentemente Acoplado demais ao pai
# BOM: Componentes com propósito claro
components/
├── common/
│   ├── AppButton.vue       # Reutilizado em tudo
│   ├── AppModal.vue        # Reutilizado em tudo
│   └── AppDataTable.vue    # Complexo, isola lógica
├── UserCard.vue            # Reutilizado em listas
└── InvoiceForm.vue         # Complexo, isola lógica

# EVITAR: Dividir demais
components/
├── UserCardHeader.vue      # Só usado dentro de UserCard
├── UserCardBody.vue        # Só usado dentro de UserCard
├── UserCardFooter.vue      # Só usado dentro de UserCard
└── UserCardAvatar.vue      # 3 linhas de HTML

7.2 Reatividade (ref, computed, watch)

<script setup lang="ts">
import { ref, computed, watch } from 'vue';

// ref() — valores reativos
const count = ref(0);
const user = ref<User | null>(null);

// computed() — valores derivados (recalculam automaticamente)
const double = computed(() => count.value * 2);
const fullName = computed(() => 
  user.value ? `${user.value.firstName} ${user.value.lastName}` : ''
);

// watch() — reagir a mudanças (efeitos colaterais)
watch(user, (newUser) => {
  if (newUser) {
    console.log('Usuário mudou:', newUser.id);
  }
});

// Métodos
function increment() {
  count.value++;
}
</script>

Regra: Usar computed() sempre que um valor deriva de outro. Evita recálculos desnecessários.

7.3 Componentes

  • Naming: PascalCase (UserCard.vue)
  • Composition API: Sempre usar <script setup lang="ts">
  • Props: Sempre tipadas com defineProps<T>()
  • Emits: Sempre tipados com defineEmits<T>()
<script setup lang="ts">
interface Props {
  userId: string;
  showDetails?: boolean;
}

interface Emits {
  (e: 'select', id: string): void;
  (e: 'delete', id: string): void;
}

const props = withDefaults(defineProps<Props>(), {
  showDetails: false,
});

const emit = defineEmits<Emits>();
</script>

<template>
  <div class="user-card">
    <!-- content -->
  </div>
</template>

<style scoped lang="scss">
.user-card {
  // styles
}
</style>

7.2 Composables

// src/composables/useUsers.ts
import { ref, computed } from 'vue';
import { userService } from '@/services/user.service';
import type { User } from '@/types';

export function useUsers() {
  const users = ref<User[]>([]);
  const loading = ref(false);
  const error = ref<string | null>(null);

  const activeUsers = computed(() => 
    users.value.filter(u => u.active)
  );

  async function fetchUsers() {
    loading.value = true;
    error.value = null;
    try {
      users.value = await userService.getAll();
    } catch (e) {
      error.value = 'Failed to fetch users';
    } finally {
      loading.value = false;
    }
  }

  return {
    users,
    loading,
    error,
    activeUsers,
    fetchUsers,
  };
}

7.3 Stores (Pinia)

// src/stores/auth.store.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import { keycloak } from '@/config/keycloak';

export const useAuthStore = defineStore('auth', () => {
  const user = ref<KeycloakProfile | null>(null);
  const isAuthenticated = computed(() => !!user.value);

  async function login() {
    await keycloak.login();
  }

  async function logout() {
    await keycloak.logout();
    user.value = null;
  }

  return {
    user,
    isAuthenticated,
    login,
    logout,
  };
});

7.4 Router Guards

// src/router/guards.ts
import type { NavigationGuard } from 'vue-router';
import { useAuthStore } from '@/stores/auth.store';

export const authGuard: NavigationGuard = (to, from, next) => {
  const authStore = useAuthStore();
  
  if (to.meta.requiresAuth && !authStore.isAuthenticated) {
    next({ name: 'login', query: { redirect: to.fullPath } });
  } else {
    next();
  }
};

8. Infraestrutura

Componente Escolha
Build Vite
Deploy Nginx / Cloudflare Pages
CI/CD GitHub Actions

Dockerfile típico

# Build stage
FROM node:22-alpine AS builder

WORKDIR /app
RUN npm install -g pnpm

COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile

COPY . .
RUN pnpm build

# Production stage
FROM nginx:alpine

COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

nginx.conf

server {
    listen 80;
    root /usr/share/nginx/html;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }

    location /api {
        proxy_pass http://backend:8080;
    }
}

9. O que NÃO usar

Tecnologia Motivo
JavaScript puro Sempre TypeScript
Options API Preferir Composition API
Vuex Preferir Pinia
Vue CLI Preferir Vite
Tailwind Preferir Bootstrap (mais familiar)
localStorage para tokens Security risk

10. Checklist de Novo Projeto

  • Criar projeto com pnpm create vue@latest
  • Selecionar: TypeScript, Vue Router, Pinia, Vitest
  • Instalar Bootstrap ou PrimeVue
  • Configurar Keycloak
  • Configurar Axios com interceptors
  • Criar estrutura de pastas
  • Configurar router guards
  • Criar layouts base
  • Configurar variáveis de ambiente
  • Criar Dockerfile + nginx.conf
  • Criar README.md com instruções

11. Links de Referência


Nota: Este template é a base. Cada projeto pode ter ajustes específicos documentados no technical_context.md do projeto.