Setup Completo - Frontend
Guia completo para criar um projeto frontend React + Vite + TanStack Router + TanStack Query do zero
Setup Completo - Frontend IngenioLab
Guia completo para criar um projeto frontend React + Vite + TanStack Router + TanStack Query do zero, seguindo os padrões da IngenioLab.
Índice
- Configuração Inicial do Projeto
- Configuração do Vite
- Setup do TypeScript
- Configuração do React
- Setup do TanStack Router
- Configuração do TanStack Query
- Setup do Tailwind CSS
- Configuração do Bootstrap (Opcional)
- Estrutura de Componentes
- Sistema de Autenticação
- Configuração de Testes
- Build e Deploy
1. Configuração Inicial do Projeto
1.1 Criar Projeto com Vite
npm create vite@latest meu-projeto-frontend -- --template react-tscd meu-projeto-frontendnpm install
1.2 Instalar Dependências Principais
# TanStack (Router + Query)npm install @tanstack/react-router @tanstack/react-querynpm install @tanstack/router-devtools @tanstack/react-query-devtools# Utilidades essenciaisnpm install axiosnpm install zodnpm install clsxnpm install lucide-react# Tailwind CSSnpm install -D tailwindcss postcss autoprefixernpm install -D @tailwindcss/forms @tailwindcss/typography# Dependências de desenvolvimentonpm install -D @types/react @types/react-domnpm install -D vitest jsdom @testing-library/react @testing-library/jest-domnpm install -D @vitejs/plugin-react
1.3 Estrutura de Pastas Inicial
mkdir -p src/{components,hooks,services,types,utils,pages,layouts,contexts,constants}mkdir -p src/components/{ui,forms,layout}mkdir -p public/images
2. Configuração do Vite
2.1 Atualizar vite.config.ts
import { defineConfig } from 'vite';import react from '@vitejs/plugin-react';import { TanStackRouterVite } from '@tanstack/router-vite-plugin';import path from 'path';export default defineConfig({plugins: [react(),TanStackRouterVite(),],resolve: {alias: {'@': path.resolve(__dirname, './src'),'@/components': path.resolve(__dirname, './src/components'),'@/pages': path.resolve(__dirname, './src/pages'),'@/hooks': path.resolve(__dirname, './src/hooks'),'@/services': path.resolve(__dirname, './src/services'),'@/utils': path.resolve(__dirname, './src/utils'),'@/types': path.resolve(__dirname, './src/types'),'@/contexts': path.resolve(__dirname, './src/contexts'),'@/constants': path.resolve(__dirname, './src/constants'),},},server: {port: 3000,host: true,},preview: {port: 3000,host: true,},build: {outDir: 'dist',sourcemap: true,rollupOptions: {output: {manualChunks: {vendor: ['react', 'react-dom'],tanstack: ['@tanstack/react-router', '@tanstack/react-query'],},},},},});
3. Setup do TypeScript
3.1 Atualizar tsconfig.json
{"compilerOptions": {"target": "ES2022","lib": ["ES2023", "DOM", "DOM.Iterable"],"module": "ESNext","skipLibCheck": true,"moduleResolution": "bundler","allowImportingTsExtensions": true,"resolveJsonModule": true,"isolatedModules": true,"noEmit": true,"jsx": "react-jsx","strict": true,"noUnusedLocals": true,"noUnusedParameters": true,"noFallthroughCasesInSwitch": true,"baseUrl": ".","paths": {"@/*": ["./src/*"],"@/components/*": ["./src/components/*"],"@/pages/*": ["./src/pages/*"],"@/hooks/*": ["./src/hooks/*"],"@/services/*": ["./src/services/*"],"@/utils/*": ["./src/utils/*"],"@/types/*": ["./src/types/*"],"@/contexts/*": ["./src/contexts/*"],"@/constants/*": ["./src/constants/*"]}},"include": ["src"],"references": [{ "path": "./tsconfig.node.json" }]}
3.2 Criar Tipos Globais
Criar src/types/global.d.ts:
export interface User {id: string;email: string;name: string;role: 'ADMIN' | 'AGENT' | 'USER';phone?: string;createdAt: string;updatedAt: string;}export interface AuthResponse {success: boolean;data: {user: User;token: string;};message?: string;}export interface ApiError {success: false;message: string;statusCode: number;stack?: string;}export interface LoginCredentials {email: string;password: string;}export interface RegisterData {name: string;email: string;password: string;phone?: string;}
4. Configuração do React
4.1 Criar Context de Autenticação
Criar src/contexts/auth-context.tsx:
import React, { createContext, useContext, useEffect, useState } from 'react';import type { User } from '@/types/global';interface AuthContextType {user: User | null;token: string | null;login: (user: User, token: string) => void;logout: () => void;isAuthenticated: boolean;isLoading: boolean;}const AuthContext = createContext<AuthContextType | undefined>(undefined);export function AuthProvider({ children }: { children: React.ReactNode }) {const [user, setUser] = useState<User | null>(null);const [token, setToken] = useState<string | null>(null);const [isLoading, setIsLoading] = useState(true);useEffect(() => {// Verificar se há token salvo no localStorageconst savedToken = localStorage.getItem('auth_token');const savedUser = localStorage.getItem('auth_user');if (savedToken && savedUser) {try {setToken(savedToken);setUser(JSON.parse(savedUser));} catch (error) {console.error('Erro ao carregar dados de autenticação:', error);localStorage.removeItem('auth_token');localStorage.removeItem('auth_user');}}setIsLoading(false);}, []);const login = (userData: User, userToken: string) => {setUser(userData);setToken(userToken);localStorage.setItem('auth_token', userToken);localStorage.setItem('auth_user', JSON.stringify(userData));};const logout = () => {setUser(null);setToken(null);localStorage.removeItem('auth_token');localStorage.removeItem('auth_user');};const isAuthenticated = !!user && !!token;return (<AuthContext.Providervalue={{user,token,login,logout,isAuthenticated,isLoading,}}>{children}</AuthContext.Provider>);}export function useAuth() {const context = useContext(AuthContext);if (context === undefined) {throw new Error('useAuth deve ser usado dentro de um AuthProvider');}return context;}
5. Setup do TanStack Router
5.1 Criar Configuração Base do Router
Criar src/routes/__root.tsx:
import { createRootRoute, Link, Outlet } from '@tanstack/react-router';import { TanStackRouterDevtools } from '@tanstack/router-devtools';export const Route = createRootRoute({component: () => (<><div className="p-2 flex gap-2"><Link to="/" className="[&.active]:font-bold">Home</Link>{' '}<Link to="/login" className="[&.active]:font-bold">Login</Link><Link to="/dashboard" className="[&.active]:font-bold">Dashboard</Link></div><hr /><Outlet /><TanStackRouterDevtools /></>),});
5.2 Criar Rota Home
Criar src/routes/index.tsx:
import { createFileRoute } from '@tanstack/react-router';export const Route = createFileRoute('/')({component: Home,});function Home() {return (<div className="p-2"><h3 className="text-3xl font-bold">Bem-vindo ao IngenioLab!</h3><p className="mt-4">Este é um projeto frontend seguindo os padrões IngenioLab.</p></div>);}
5.3 Criar Rota de Login
Criar src/routes/login.tsx:
import { createFileRoute, redirect } from '@tanstack/react-router';import { LoginForm } from '@/components/forms/login-form';import { useAuth } from '@/contexts/auth-context';export const Route = createFileRoute('/login')({beforeLoad: ({ context }) => {const { isAuthenticated } = context as { isAuthenticated: boolean };if (isAuthenticated) {throw redirect({to: '/dashboard',});}},component: Login,});function Login() {return (<div className="min-h-screen flex items-center justify-center bg-gray-50"><div className="max-w-md w-full space-y-8"><div><h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">Fazer login na conta</h2></div><LoginForm /></div></div>);}
5.4 Criar Rota Protegida
Criar src/routes/dashboard.tsx:
import { createFileRoute, redirect } from '@tanstack/react-router';import { useAuth } from '@/contexts/auth-context';export const Route = createFileRoute('/dashboard')({beforeLoad: ({ context }) => {const { isAuthenticated } = context as { isAuthenticated: boolean };if (!isAuthenticated) {throw redirect({to: '/login',});}},component: Dashboard,});function Dashboard() {const { user, logout } = useAuth();return (<div className="p-2"><div className="flex justify-between items-center mb-6"><h3 className="text-3xl font-bold">Dashboard</h3><buttononClick={logout}className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700">Logout</button></div><div className="bg-white shadow rounded-lg p-6"><h4 className="text-xl font-semibold mb-4">Informações do Usuário</h4><dl className="grid grid-cols-1 gap-x-4 gap-y-4 sm:grid-cols-2"><div><dt className="text-sm font-medium text-gray-500">Nome</dt><dd className="mt-1 text-sm text-gray-900">{user?.name}</dd></div><div><dt className="text-sm font-medium text-gray-500">Email</dt><dd className="mt-1 text-sm text-gray-900">{user?.email}</dd></div><div><dt className="text-sm font-medium text-gray-500">Role</dt><dd className="mt-1 text-sm text-gray-900">{user?.role}</dd></div><div><dt className="text-sm font-medium text-gray-500">Telefone</dt><dd className="mt-1 text-sm text-gray-900">{user?.phone || 'Não informado'}</dd></div></dl></div></div>);}
6. Configuração do TanStack Query
6.1 Criar Configuração do Query Client
Criar src/services/query-client.ts:
import { QueryClient } from '@tanstack/react-query';export const queryClient = new QueryClient({defaultOptions: {queries: {staleTime: 5 * 60 * 1000, // 5 minutosgcTime: 10 * 60 * 1000, // 10 minutosretry: (failureCount, error: any) => {if (error?.response?.status === 401) {return false; // Não retry para erros de auth}return failureCount < 2;},},mutations: {retry: false,},},});
6.2 Criar Service de API
Criar src/services/api.ts:
import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios';import type { AuthResponse, LoginCredentials, RegisterData } from '@/types/global';class ApiService {private api: AxiosInstance;constructor() {this.api = axios.create({baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3333/api',timeout: 10000,});// Interceptor para adicionar token nas requisiçõesthis.api.interceptors.request.use((config) => {const token = localStorage.getItem('auth_token');if (token) {config.headers.Authorization = `Bearer ${token}`;}return config;});// Interceptor para tratar erros de respostathis.api.interceptors.response.use((response) => response,(error) => {if (error.response?.status === 401) {localStorage.removeItem('auth_token');localStorage.removeItem('auth_user');window.location.href = '/login';}return Promise.reject(error);});}async login(credentials: LoginCredentials): Promise<AuthResponse> {const response = await this.api.post<AuthResponse>('/auth/login', credentials);return response.data;}async register(data: RegisterData): Promise<AuthResponse> {const response = await this.api.post<AuthResponse>('/auth/register', data);return response.data;}async getProfile() {const response = await this.api.get('/auth/profile');return response.data;}async get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {const response = await this.api.get<T>(url, config);return response.data;}async post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {const response = await this.api.post<T>(url, data, config);return response.data;}async put<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {const response = await this.api.put<T>(url, data, config);return response.data;}async delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {const response = await this.api.delete<T>(url, config);return response.data;}}export const apiService = new ApiService();
6.3 Criar Hooks de Autenticação
Criar src/hooks/use-auth-mutations.ts:
import { useMutation, useQueryClient } from '@tanstack/react-query';import { useAuth } from '@/contexts/auth-context';import { apiService } from '@/services/api';import type { LoginCredentials, RegisterData } from '@/types/global';export function useLoginMutation() {const { login } = useAuth();const queryClient = useQueryClient();return useMutation({mutationFn: (credentials: LoginCredentials) => apiService.login(credentials),onSuccess: (data) => {login(data.data.user, data.data.token);queryClient.invalidateQueries({ queryKey: ['profile'] });},});}export function useRegisterMutation() {const { login } = useAuth();const queryClient = useQueryClient();return useMutation({mutationFn: (data: RegisterData) => apiService.register(data),onSuccess: (data) => {login(data.data.user, data.data.token);queryClient.invalidateQueries({ queryKey: ['profile'] });},});}export function useLogoutMutation() {const { logout } = useAuth();const queryClient = useQueryClient();return useMutation({mutationFn: async () => {// Aqui você pode fazer uma chamada para o backend se necessário// await apiService.post('/auth/logout');},onSuccess: () => {logout();queryClient.clear();},});}
7. Setup do Tailwind CSS
7.1 Configurar Tailwind
npx tailwindcss init -p
7.2 Atualizar tailwind.config.js
/** @type {import('tailwindcss').Config} */export default {content: ["./index.html","./src/**/*.{js,ts,jsx,tsx}",],theme: {extend: {colors: {primary: {50: '#eff6ff',500: '#3b82f6',600: '#2563eb',700: '#1d4ed8',},gray: {50: '#f9fafb',100: '#f3f4f6',200: '#e5e7eb',300: '#d1d5db',400: '#9ca3af',500: '#6b7280',600: '#4b5563',700: '#374151',800: '#1f2937',900: '#111827',},},},},plugins: [require('@tailwindcss/forms'),require('@tailwindcss/typography'),],}
7.3 Adicionar Estilos Base
Atualizar src/index.css:
@tailwind base;@tailwind components;@tailwind utilities;@layer base {html {font-family: 'Inter', system-ui, sans-serif;}}@layer components {.btn {@apply font-medium rounded-lg text-sm px-5 py-2.5 text-center;}.btn-primary {@apply text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:ring-primary-300;}.btn-secondary {@apply text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-primary-700 focus:z-10 focus:ring-4 focus:ring-gray-200;}.form-input {@apply appearance-none rounded-md relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-primary-500 focus:border-primary-500 focus:z-10 sm:text-sm;}.form-label {@apply block text-sm font-medium text-gray-700;}}
8. Configuração do Bootstrap (Opcional)
8.1 Instalar Bootstrap
npm install bootstrapnpm install -D @types/bootstrap
8.2 Importar Bootstrap (se escolher usar)
Em src/main.tsx:
import 'bootstrap/dist/css/bootstrap.min.css';
9. Estrutura de Componentes
9.1 Criar Componente de Input
Criar src/components/ui/input.tsx:
import React from 'react';import { clsx } from 'clsx';interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {label?: string;error?: string;variant?: 'default' | 'error';}export function Input({label,error,variant = 'default',className,...props}: InputProps) {return (<div className="space-y-1">{label && (<label htmlFor={props.id} className="form-label">{label}</label>)}<inputclassName={clsx('form-input',variant === 'error' && 'border-red-300 focus:border-red-500 focus:ring-red-500',className)}{...props}/>{error && (<p className="text-sm text-red-600">{error}</p>)}</div>);}
9.2 Criar Componente de Button
Criar src/components/ui/button.tsx:
import React from 'react';import { clsx } from 'clsx';interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {variant?: 'primary' | 'secondary' | 'danger';size?: 'sm' | 'md' | 'lg';isLoading?: boolean;}export function Button({children,variant = 'primary',size = 'md',isLoading = false,className,disabled,...props}: ButtonProps) {return (<buttonclassName={clsx('btn',variant === 'primary' && 'btn-primary',variant === 'secondary' && 'btn-secondary',variant === 'danger' && 'text-white bg-red-600 hover:bg-red-700 focus:ring-4 focus:ring-red-300',size === 'sm' && 'px-3 py-2 text-xs',size === 'lg' && 'px-6 py-3 text-base','disabled:opacity-50 disabled:cursor-not-allowed',className)}disabled={disabled || isLoading}{...props}>{isLoading && (<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle><path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>)}{children}</button>);}
9.3 Criar Formulário de Login
Criar src/components/forms/login-form.tsx:
import React from 'react';import { useForm } from 'react-hook-form';import { zodResolver } from '@hookform/resolvers/zod';import { z } from 'zod';import { useNavigate } from '@tanstack/react-router';import { Input } from '@/components/ui/input';import { Button } from '@/components/ui/button';import { useLoginMutation } from '@/hooks/use-auth-mutations';const loginSchema = z.object({email: z.string().email('Email inválido'),password: z.string().min(1, 'Senha é obrigatória'),});type LoginFormData = z.infer<typeof loginSchema>;export function LoginForm() {const navigate = useNavigate();const loginMutation = useLoginMutation();const {register,handleSubmit,formState: { errors },} = useForm<LoginFormData>({resolver: zodResolver(loginSchema),});const onSubmit = async (data: LoginFormData) => {try {await loginMutation.mutateAsync(data);navigate({ to: '/dashboard' });} catch (error) {console.error('Erro no login:', error);}};return (<form className="mt-8 space-y-6" onSubmit={handleSubmit(onSubmit)}><div className="space-y-4"><Inputid="email"type="email"label="Email"placeholder="seu@email.com"error={errors.email?.message}{...register('email')}/><Inputid="password"type="password"label="Senha"placeholder="Sua senha"error={errors.password?.message}{...register('password')}/></div>{loginMutation.error && (<div className="text-red-600 text-sm">{loginMutation.error?.response?.data?.message || 'Erro ao fazer login'}</div>)}<Buttontype="submit"className="w-full"isLoading={loginMutation.isPending}>Entrar</Button></form>);}
10. Sistema de Autenticação
10.1 Atualizar Main App
Atualizar src/main.tsx:
import React from 'react';import ReactDOM from 'react-dom/client';import { QueryClient, QueryClientProvider } from '@tanstack/react-query';import { ReactQueryDevtools } from '@tanstack/react-query-devtools';import { createRouter, RouterProvider } from '@tanstack/react-router';import { routeTree } from './routeTree.gen';import { AuthProvider, useAuth } from '@/contexts/auth-context';import { queryClient } from '@/services/query-client';import './index.css';// Criar router com context de autenticaçãoconst router = createRouter({routeTree,context: {isAuthenticated: false,},});declare module '@tanstack/react-router' {interface Register {router: typeof router;}}function App() {const { isAuthenticated } = useAuth();// Atualizar context do router quando auth mudarReact.useEffect(() => {router.invalidate();}, [isAuthenticated]);return (<RouterProviderrouter={router}context={{ isAuthenticated }}/>);}ReactDOM.createRoot(document.getElementById('root')!).render(<React.StrictMode><QueryClientProvider client={queryClient}><AuthProvider><App /></AuthProvider><ReactQueryDevtools initialIsOpen={false} /></QueryClientProvider></React.StrictMode>,);
10.2 Criar Variáveis de Ambiente
Criar .env:
VITE_API_URL=http://localhost:3333/api
Criar .env.example:
VITE_API_URL=http://localhost:3333/api
11. Configuração de Testes
11.1 Configurar Vitest
Criar vitest.config.ts:
/// <reference types="vitest" />import { defineConfig } from 'vite';import react from '@vitejs/plugin-react';import path from 'path';export default defineConfig({plugins: [react()],test: {globals: true,environment: 'jsdom',setupFiles: ['./src/test/setup.ts'],},resolve: {alias: {'@': path.resolve(__dirname, './src'),},},});
11.2 Criar Setup de Teste
Criar src/test/setup.ts:
import { expect, afterEach } from 'vitest';import { cleanup } from '@testing-library/react';import * as matchers from '@testing-library/jest-dom/matchers';expect.extend(matchers);afterEach(() => {cleanup();});
11.3 Criar Teste de Exemplo
Criar src/components/ui/button.test.tsx:
import { describe, it, expect } from 'vitest';import { render, screen } from '@testing-library/react';import { Button } from './button';describe('Button', () => {it('renderiza correctamente', () => {render(<Button>Clique aqui</Button>);expect(screen.getByRole('button', { name: /clique aqui/i })).toBeInTheDocument();});it('mostra loading state', () => {render(<Button isLoading>Carregando</Button>);expect(screen.getByRole('button')).toBeDisabled();});it('aplica variante primary por padrão', () => {render(<Button>Primary Button</Button>);const button = screen.getByRole('button');expect(button).toHaveClass('btn-primary');});});
12. Build e Deploy
12.1 Adicionar Scripts ao package.json
{"scripts": {"dev": "vite","build": "tsc && vite build","lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0","preview": "vite preview","test": "vitest","test:ui": "vitest --ui","test:coverage": "vitest --coverage"}}
12.2 Build para Produção
# Build do projetonpm run build# Preview do buildnpm run preview
12.3 Configurar Análise de Bundle
npm install -D rollup-plugin-visualizer
Atualizar vite.config.ts:
import { visualizer } from 'rollup-plugin-visualizer';export default defineConfig({plugins: [react(),TanStackRouterVite(),...(process.env.ANALYZE ? [visualizer({ open: true })] : []),],// ... resto da config});
12.4 Analisar Bundle
ANALYZE=true npm run build
Estrutura Final do Projeto
meu-projeto-frontend/├── public/│ ├── vite.svg│ └── images/├── src/│ ├── components/│ │ ├── ui/│ │ │ ├── button.tsx│ │ │ ├── input.tsx│ │ │ └── button.test.tsx│ │ ├── forms/│ │ │ └── login-form.tsx│ │ └── layout/│ ├── contexts/│ │ └── auth-context.tsx│ ├── hooks/│ │ └── use-auth-mutations.ts│ ├── pages/│ ├── routes/│ │ ├── __root.tsx│ │ ├── index.tsx│ │ ├── login.tsx│ │ └── dashboard.tsx│ ├── services/│ │ ├── api.ts│ │ └── query-client.ts│ ├── test/│ │ └── setup.ts│ ├── types/│ │ └── global.d.ts│ ├── utils/│ ├── index.css│ ├── main.tsx│ └── vite-env.d.ts├── .env├── .env.example├── index.html├── package.json├── tailwind.config.js├── tsconfig.json├── vite.config.ts└── vitest.config.ts
Próximos Passos
Com este setup você terá:
✅ React + TypeScript configurado corretamente
✅ Vite como build tool otimizada
✅ TanStack Router para roteamento type-safe
✅ TanStack Query para gerenciamento de estado server
✅ Tailwind CSS para estilização
✅ Sistema de autenticação funcional
✅ Formulários validados com React Hook Form + Zod
✅ Testes configurados com Vitest
✅ TypeScript rigoroso e paths configurados
Expansões Recomendadas
- Componentes UI mais completos
- Formulários avançados com validação complexa
- Tabelas com paginação e filtros
- Upload de arquivos
- Notificações toast
- Temas dark/light
- PWA capabilities
- Lazy loading de rotas
- Error boundaries
- Internacionalização (i18n)
Este setup fornece uma base sólida seguindo as melhores práticas da IngenioLab para desenvolvimento frontend moderno.