Fumadocs + Code Hike
Frontend

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

  1. Configuração Inicial do Projeto
  2. Configuração do Vite
  3. Setup do TypeScript
  4. Configuração do React
  5. Setup do TanStack Router
  6. Configuração do TanStack Query
  7. Setup do Tailwind CSS
  8. Configuração do Bootstrap (Opcional)
  9. Estrutura de Componentes
  10. Sistema de Autenticação
  11. Configuração de Testes
  12. Build e Deploy

1. Configuração Inicial do Projeto

1.1 Criar Projeto com Vite

npm create vite@latest meu-projeto-frontend -- --template react-ts
cd meu-projeto-frontend
npm install

1.2 Instalar Dependências Principais

# TanStack (Router + Query)
npm install @tanstack/react-router @tanstack/react-query
npm install @tanstack/router-devtools @tanstack/react-query-devtools
# Utilidades essenciais
npm install axios
npm install zod
npm install clsx
npm install lucide-react
# Tailwind CSS
npm install -D tailwindcss postcss autoprefixer
npm install -D @tailwindcss/forms @tailwindcss/typography
# Dependências de desenvolvimento
npm install -D @types/react @types/react-dom
npm install -D vitest jsdom @testing-library/react @testing-library/jest-dom
npm 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 localStorage
const 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.Provider
value={{
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>
<button
onClick={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 minutos
gcTime: 10 * 60 * 1000, // 10 minutos
retry: (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ções
this.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 resposta
this.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 bootstrap
npm 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>
)}
<input
className={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 (
<button
className={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">
<Input
id="email"
type="email"
label="Email"
placeholder="seu@email.com"
error={errors.email?.message}
{...register('email')}
/>
<Input
id="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>
)}
<Button
type="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ção
const 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 mudar
React.useEffect(() => {
router.invalidate();
}, [isAuthenticated]);
return (
<RouterProvider
router={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 projeto
npm run build
# Preview do build
npm 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.

On this page