O Mapa Completo do Dev Web Moderno: Ferramentas, Decisões e Anti-Patterns que Separam Júnior de Sênior

Conteúdo técnico toda semana
Receba artigos sobre arquitetura, padrões de projeto e engenharia de software. Direto no seu e-mail, sem enrolação.
Sem spam. Cancele a qualquer momento com 1 clique.
Neste artigo
Resumo rápido
Este artigo é um sistema de decisão para montar sua stack web em 2026. Você sai daqui com: um framework de 5 passos para escolher qualquer tecnologia, tabelas comparativas com dados de downloads e benchmarks reais, anti-patterns com código errado vs correto, e um roadmap progressivo de 5 níveis de maturidade. Não é um catálogo de ferramentas. É um guia de engenharia que eu uso na Agência Poti para onboarding de devs plenos e seniors.
O problema: por que 90% dos mapas de stack falham
Em 2019, montei um "mapa de tecnologias" para o time de um cliente no setor de logística. Era bonito: caixas coloridas, setas conectando HTML até Kubernetes, um roadmap linear de 18 meses. Três meses depois, dois devs saíram frustrados porque o mapa dizia o que aprender, mas não por que escolher uma coisa e não outra.
O problema de todo "mapa completo" é que ele vira catálogo. Lista 47 tecnologias, coloca um check ao lado de cada uma, e o leitor fecha a aba sem saber responder a pergunta que importa: qual stack eu monto para o meu cenário específico?
Eu cometi esse erro no primeiro artigo que escrevi sobre esse tema. Listei frameworks, build tools, bibliotecas de estado. Tudo correto, tudo raso. Desde então, operei com mais de 30 projetos em produção na Agência Poti e em consultorias, e o que aprendi é direto: dev sênior não precisa de lista. Precisa de critério de decisão.
Este artigo substitui catálogo por sistema. Cada seção tem valor independente. Você lê só a parte de testes e sai sabendo migrar de Jest para Vitest. Lê só a parte de deploy e sai com uma decision tree para escolher entre Vercel, AWS e VPS. Essa é a proposta.
Framework de decisão: como escolher qualquer stack em 5 passos
Eu uso esses 5 passos em toda consultoria de arquitetura. Funcionam para escolher framework, ORM, cloud provider, qualquer coisa.
Passo 1: Defina o constraint primário
Todo projeto tem um gargalo dominante. Identifique qual é o seu:
- Tempo: MVP em 3 meses, precisa de velocidade de desenvolvimento
- Escala de time: 15+ devs, precisa de conventions fortes e tipagem rígida
- Performance: dashboard real-time, precisa de bundle mínimo e rendering otimizado
- Custo: freelancer solo, precisa de deploy barato e ecossistema maduro
- Contratação: mercado BR, precisa de pool de candidatos disponíveis
Em 2023, um cliente de fintech me pediu para escolher entre Svelte e React. O constraint primário era contratação: eles precisavam de 8 devs em São Paulo em 60 dias. Svelte tem 3.3M de downloads semanais. React tem 96M. A resposta se deu sozinha.
Passo 2: Elimine por deal-breakers
Não compare 5 opções. Elimine as que não passam no filtro mínimo:
// Decision filter - aplique antes de comparar qualquer coisa
interface StackFilter {
hasTypescriptSupport: boolean; // Eliminatório em 2026
weeklyDownloads: number; // < 1M = risco de abandono
lastMajorRelease: Date; // > 18 meses = red flag
brJobPostings: number; // GeekHunter + Gupy, últimos 90 dias
teamExperience: 'none' | 'some' | 'strong';
}
function shouldEliminate(filter: StackFilter): boolean {
if (!filter.hasTypescriptSupport) return true;
if (filter.weeklyDownloads < 500_000) return true;
if (daysSince(filter.lastMajorRelease) > 540) return true;
if (filter.brJobPostings < 50 && filter.teamExperience === 'none') return true;
return false;
}Passo 3: Pese ecossistema sobre features
Feature individual é irrelevante se o ecossistema não sustenta. Eu avalio ecossistema por 3 métricas: número de pacotes compatíveis no npm, qualidade da documentação oficial, e frequência de respostas no Stack Overflow/GitHub Discussions nos últimos 6 meses.
Em 2021, escolhi Solid.js para um projeto pessoal. Performance absurda. Mas quando precisei de uma lib de formulários com validação server-side, gastei 3 dias adaptando algo que no React levaria 20 minutos com React Hook Form.
Passo 4: Prototipe em 4 horas, não em 4 semanas
Monte o fluxo mais crítico do seu app nas 2-3 opções finalistas. Meça: tempo de setup, clareza do código, e se a documentação respondeu suas dúvidas sem precisar de Stack Overflow.
# Cronômetro real: monte um CRUD com auth em cada framework
# Meça tempo total do zero ao deploy
# Next.js (App Router)
npx create-next-app@latest --typescript --tailwind --app
# Meu tempo médio: 3h42min para CRUD + auth + deploy na Vercel
# SvelteKit
npx sv create my-app
# Meu tempo médio: 4h15min (menos familiaridade, docs excelentes)
# Nuxt 3
npx nuxi@latest init my-app
# Meu tempo médio: 3h58min (Vue familiar, ecossistema menor)Passo 5: Valide com o cenário de 18 meses
Pergunte: "Se eu precisar dobrar o time em 18 meses, consigo contratar? Se o framework lançar breaking changes, a migração é viável? Se o maintainer principal sair, o projeto sobrevive?"
Rich Harris, criador do Svelte, foi contratado pela Vercel. Evan You, criador do Vue e Vite, mantém ambos com funding independente. Dan Abramov saiu do time core do React. Essas movimentações importam mais do que benchmarks de bundle size.
Frameworks em 2026: dados, não opinião
O State of JS 2025 trouxe um dado que deveria ter encerrado a conversa sobre "framework fatigue": a média de frameworks que um dev usa na carreira inteira é 2.6. Não 2.6 por ano. Por carreira.
Isso significa que a escolha do framework é uma das decisões de maior impacto na sua trajetória. Vamos olhar os números.
Tabela comparativa: React vs Vue vs Svelte
| Métrica | React | Vue | Svelte |
|---|---|---|---|
| Downloads/semana (npm) | 96M | 9M | 3.3M |
| Bundle min+gzip | 44.5 KB | 33 KB | 1.8 KB* |
| Arquitetura | VDOM, runtime | VDOM + reatividade | Compile-time, sem VDOM |
| TypeScript | Nativo (JSX) | Nativo (Vue 3+) | Nativo (Svelte 5+) |
| Meta-framework | Next.js (6.5M/sem) | Nuxt (1.2M/sem) | SvelteKit (450K/sem) |
| Satisfação (State of JS 2025) | 69% | 72% | 88% |
| Vagas BR (GeekHunter, Q1 2026) | ~4.200 | ~680 | ~120 |
O asterisco no Svelte é crucial. O bundle de 1.8 KB é o runtime base. Cada componente Svelte adiciona código compilado. Em apps com 19+ componentes do tamanho de um TodoMVC, o bundle total do Svelte ultrapassa o do React. Esse dado vem de benchmarks reproduzíveis do framework-benchmarks do krausest no GitHub. Svelte é mais leve para landing pages e apps pequenos. Para dashboards com 50+ componentes, a vantagem desaparece.
O moat do Angular em bancos brasileiros
Angular tem um fenômeno único no Brasil que não existe em nenhum outro mercado. Itaú, Bradesco, TOTVS e Banco do Brasil operam frontends críticos em Angular. O motivo não é performance. É que Angular impõe conventions: módulos, injeção de dependência, estrutura de pastas, tudo padronizado. Com times de 40+ devs, isso reduz o caos.
Eu consultei para uma fintech que integrava com APIs do Itaú em 2022. O requisito contratual era Angular. Sem negociação. Se você mira enterprise financeiro no Brasil, Angular não é opção. É pré-requisito.
Astro: a métrica que ninguém reporta
O Astro tem 39 pontos de satisfação acima do Next.js no State of JS 2025. Essa é a métrica mais sub-reportada do ecossistema. Para projetos content-first (blogs, docs, landing pages, sites de marketing), Astro entrega zero JavaScript por default e permite "islands" de React, Vue ou Svelte onde interatividade é necessária.
Eu migrei o Vivo de Código para uma arquitetura similar e o LCP caiu de 2.1s para 890ms. Zero JS no client para páginas de artigo. Islands de React só nos componentes interativos.
O shift arquitetural: server state vs client state
A maior mudança arquitetural no frontend dos últimos 3 anos não foi um framework novo. Foi a separação entre server state (dados que vêm de APIs) e client state (UI state local: modais abertos, filtros selecionados, tema dark/light).
Redux misturava tudo num store gigante. Em 2026, isso é um anti-pattern. A combinação TanStack Query + Zustand resolve 95% dos casos com menos código e melhor performance.
Server state com TanStack Query
TanStack Query (12M downloads/semana) cuida de cache, refetch, stale-while-revalidate, e sincronização com o servidor. Tudo que Redux Toolkit Query tentou fazer, mas com API mais limpa.
// Server state: dados que vêm da API
// TanStack Query gerencia cache, loading, error, refetch
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
interface User {
id: string;
name: string;
email: string;
}
// Buscar lista de usuários com cache automático de 5 minutos
function useUsers() {
return useQuery<User[]>({
queryKey: ['users'],
queryFn: async () => {
const res = await fetch('/api/users');
if (!res.ok) throw new Error('Falha ao buscar usuários');
return res.json();
},
staleTime: 5 * 60 * 1000, // 5 minutos antes de considerar stale
gcTime: 10 * 60 * 1000, // 10 minutos no garbage collection
});
}
// Mutation com invalidação automática do cache
function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (newUser: Omit<User, 'id'>) => {
const res = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newUser),
});
return res.json();
},
onSuccess: () => {
// Invalida o cache e força refetch
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
}Client state com Zustand
Zustand (11M downloads/semana, +30% ano a ano) é para estado que existe só no client. Sem boilerplate, sem providers, sem reducers.
// Client state: UI state que não vem do servidor
// Zustand é minimalista e performático
import { create } from 'zustand';
interface UIState {
sidebarOpen: boolean;
theme: 'light' | 'dark';
activeFilters: string[];
toggleSidebar: () => void;
setTheme: (theme: 'light' | 'dark') => void;
toggleFilter: (filter: string) => void;
}
// Store sem provider, sem boilerplate
export const useUIStore = create<UIState>((set) => ({
sidebarOpen: false,
theme: 'light',
activeFilters: [],
toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
setTheme: (theme) => set({ theme }),
toggleFilter: (filter) =>
set((state) => ({
activeFilters: state.activeFilters.includes(filter)
? state.activeFilters.filter((f) => f !== filter)
: [...state.activeFilters, filter],
})),
}));
// Uso no componente: subscribe granular, sem re-render desnecessário
// Zustand re-renderiza APENAS componentes que usam o slice que mudou
function Sidebar() {
const sidebarOpen = useUIStore((state) => state.sidebarOpen);
const toggleSidebar = useUIStore((state) => state.toggleSidebar);
// ...
}Performance comparada de state managers
| State Manager | Re-render 1000 subscribers | Bundle size | Boilerplate |
|---|---|---|---|
| Zustand | ~12ms | 1.1 KB | Mínimo |
| Jotai | ~14ms | 2.4 KB | Mínimo (atômico) |
| Redux Toolkit | ~18ms | 11 KB | Moderado |
| Redux + Saga | ~22ms | 18 KB | Alto |
Em um dashboard de monitoramento que construí para um cliente de telecom em 2024, migrei de Redux Toolkit para Zustand + TanStack Query. O bundle do módulo de estado caiu de 34 KB para 8.2 KB. O tempo de re-render em telas com 200+ elementos caiu de 45ms para 16ms. A migração levou 4 dias para 2 devs. Se você está começando projeto novo com Redux em 2026, leia a seção de anti-patterns antes.
Build tools: Vite venceu, e agora?
Webpack dominou por 8 anos. Vite encerrou essa era. Os números são categóricos:
# Benchmark reproduzível: projeto React + TypeScript com 120 componentes
# Vite 6 (esbuild + Rollup)
$ time npm run dev
# Cold start: 2.8s
# HMR: 45ms (módulo individual)
# Production build: 8.2s
# Webpack 5 (equivalente CRA)
$ time npm start
# Cold start: 43s
# HMR: 2.1s (rebuild parcial)
# Production build: 34sA diferença não é 2x. É 15x no cold start. Eu perdi anos da minha vida esperando Webpack rebuildar. Quando migrei o projeto principal da Agência Poti de Webpack para Vite em 2023, o ciclo de feedback do time caiu de "alt-tab, esperar, voltar" para "salvar e já ver". Produtividade medida em PRs por dia subiu 23% no primeiro mês.
Webpack ainda faz sentido?
Em um único cenário: projetos legados com configuração Webpack complexa (Module Federation, custom loaders proprietários) onde o custo de migração excede 2 sprints. Para projeto novo, não existe justificativa.
Turbopack: o wildcard
Turbopack, escrito em Rust pelo time da Vercel, é o bundler default do Next.js em dev mode desde o Next.js 15. Em produção, ainda usa Webpack internamente (Next.js 16 planeja migração completa). Se você já está no ecossistema Next.js, Turbopack chega sem esforço. Se está fora, Vite é a escolha. Escrevi mais sobre isso em Desbloqueie o Poder do Next.js com o Turbopack.
Evan You (criador do Vite) anunciou o Rolldown, um substituto do Rollup escrito em Rust, que será integrado ao Vite 7. A guerra dos bundlers em Rust está apenas começando, mas para o dev que usa a ferramenta, a interface não muda. npm run dev continua funcionando.
Testes: Vitest + Playwright como padrão
Vitest matou o Jest para projetos novos
Vitest roda no mesmo pipeline do Vite. Mesma config, mesmos transforms, mesmos aliases de path. O cold start é 5.6x mais rápido que o Jest porque não precisa de transformação Babel separada.
// vitest.config.ts - configuração mínima que herda do vite.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
coverage: {
provider: 'v8', // Mais rápido que istanbul
reporter: ['text', 'json', 'html'],
thresholds: {
branches: 80,
functions: 80,
lines: 80,
},
},
// Watch mode inteligente: só roda testes de arquivos alterados
// Jest faz isso também, mas Vitest é instantâneo por usar o HMR do Vite
},
});// Exemplo de teste com Vitest - API idêntica ao Jest
// Migração: trocar imports e config, testes ficam iguais
import { describe, it, expect, vi } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useUsers } from './useUsers';
function createWrapper() {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
return ({ children }: { children: React.ReactNode }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
}
describe('useUsers', () => {
it('retorna lista de usuários do servidor', async () => {
// vi.fn() é equivalente ao jest.fn()
const mockUsers = [{ id: '1', name: 'Marcos', email: '[email protected]' }];
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve(mockUsers),
});
const { result } = renderHook(() => useUsers(), {
wrapper: createWrapper(),
});
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data).toEqual(mockUsers);
});
});Migrei 340 testes de Jest para Vitest em um projeto Next.js. Tempo total: 6 horas (1 dev). O CI que rodava em 4min12s caiu para 1min48s. A migração é mecânica: troca jest.fn() por vi.fn(), ajusta config, e roda. Se você quer entender melhor o pipeline de CI, escrevi sobre isso em Como configurar CI/CD para Next.js na Vercel com GitHub Actions.
Playwright substituiu Cypress
Playwright atingiu 8M downloads/semana e 92% de satisfação. Cypress está em declínio. Os motivos são técnicos:
- Playwright roda testes em paralelo por default (Cypress é serial)
- Suporta múltiplos browsers nativamente (Chromium, Firefox, WebKit)
- Não injeta código no browser (Cypress roda dentro do browser, limitando acesso a múltiplas tabs/origens)
// playwright.config.ts - configuração para projeto Next.js
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true, // Paralelo por default
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 4 : undefined,
reporter: [['html'], ['json', { outputFile: 'results.json' }]],
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry', // Trace completo só quando falha
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'mobile', use: { ...devices['Pixel 5'] } },
],
webServer: {
command: 'npm run dev',
port: 3000,
reuseExistingServer: !process.env.CI,
},
});Performance real: o que medir e como
Bundle size é red herring se você não mede o que o usuário sente. As métricas que importam são as Core Web Vitals: LCP (Largest Contentful Paint), INP (Interaction to Next Paint), e CLS (Cumulative Layout Shift).
O que medir
| Métrica | Bom | Ruim | Como medir |
|---|---|---|---|
| LCP | < 2.5s | > 4.0s | Lighthouse, CrUX, web-vitals lib |
| INP | < 200ms | > 500ms | web-vitals lib, Chrome DevTools |
| CLS | < 0.1 | > 0.25 | Lighthouse, Layout Instability API |
| TTFB | < 800ms | > 1800ms | Server timing, Vercel Analytics |
| Bundle size (JS) | < 100 KB gzip | > 300 KB gzip | npx vite-bundle-visualizer |
Code splitting que funciona
// Errado: importar tudo no bundle principal
import { Chart } from 'recharts';
import { DataGrid } from '@mui/x-data-grid';
import { Editor } from '@monaco-editor/react';
// Correto: lazy loading com Suspense por rota/feature
import { lazy, Suspense } from 'react';
const Chart = lazy(() => import('recharts').then(m => ({ default: m.BarChart })));
const DataGrid = lazy(() => import('@mui/x-data-grid').then(m => ({ default: m.DataGrid })));
const Editor = lazy(() => import('@monaco-editor/react'));
// Componente com fallback adequado (não um spinner genérico)
function DashboardChart({ data }: { data: ChartData[] }) {
return (
<Suspense fallback={<div className="h-[400px] animate-pulse bg-gray-100 rounded" />}>
<Chart data={data} />
</Suspense>
);
}Em um projeto de e-commerce, apliquei code splitting por rota e lazy loading para componentes pesados (editor de imagem, gráficos de analytics). O bundle inicial caiu de 420 KB para 89 KB gzip. O LCP em 3G simulado caiu de 4.8s para 1.9s. A técnica é simples. O impacto é brutal.
Para estratégias avançadas de cache que complementam code splitting, veja Caching no Next.js: ISR, On-Demand e Stale-While-Revalidate.
Deploy: Vercel vs AWS vs VPS
Decision tree por cenário
PRECISA de edge functions + preview deploys + zero config?
├── SIM → Vercel ou Netlify
│ ├── Orçamento < R$500/mês → Vercel Pro (R$100/mês)
│ └── Orçamento > R$500/mês → Vercel Enterprise ou AWS
└── NÃO
├── Precisa de controle total sobre infra?
│ ├── SIM → AWS (ECS/Fargate) ou VPS (Hetzner/DigitalOcean)
│ │ ├── Time tem devops? → AWS com Terraform
│ │ └── Sem devops? → VPS com Docker Compose
│ └── NÃO → Vercel
└── Projeto interno/intranet?
└── VPS ou on-premise| Cenário | Recomendação | Custo mensal estimado | Complexidade |
|---|---|---|---|
| Freelancer / portfolio | Vercel Hobby (free) | R$0 | Baixa |
| Startup MVP | Vercel Pro | R$100-500 | Baixa |
| SaaS em crescimento | AWS ECS + CloudFront | R$800-3000 | Alta |
| Enterprise regulado | AWS/GCP com VPC dedicada | R$5000+ | Muito alta |
| Blog / docs | Vercel ou Cloudflare Pages | R$0-50 | Mínima |
Em 2024, migrei um SaaS de Vercel Pro para AWS ECS quando a conta chegou a R$2.800/mês. Na AWS, o mesmo tráfego custa R$1.100/mês, mas precisei de 3 semanas para configurar ECS, ALB, CloudFront, e Route 53. O break-even foi em 5 meses. Para quem quer entender o setup completo de infra, escrevi sobre Docker e CI/CD em DevOps para Devs.
O que NÃO fazer: anti-patterns que custam semanas
Anti-pattern 1: Redux para server state
// ❌ ERRADO: Redux gerenciando dados do servidor
// Resultado: 200+ linhas para um CRUD simples
const usersSlice = createSlice({
name: 'users',
initialState: {
data: [] as User[],
loading: false,
error: null as string | null,
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUsers.pending, (state) => { state.loading = true; })
.addCase(fetchUsers.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(fetchUsers.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message ?? 'Erro';
});
// Repita para create, update, delete...
// + cache manual, + invalidação manual, + refetch manual
},
});
// ✅ CORRETO: TanStack Query para server state
// Resultado: 15 linhas, cache automático, refetch automático
const { data, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(r => r.json()),
staleTime: 5 * 60 * 1000,
});Anti-pattern 2: useEffect com dependências erradas
// ❌ ERRADO: re-render infinito
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
// Objeto criado a cada render → referência muda → loop infinito
const options = { includeAvatar: true };
fetchUser(userId, options).then(setUser);
}, [userId, { includeAvatar: true }]); // ← objeto literal na dependência
return <div>{user?.name}</div>;
}
// ✅ CORRETO: dependências estáveis
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
const controller = new AbortController();
fetchUser(userId, { includeAvatar: true, signal: controller.signal })
.then(setUser)
.catch((err) => {
if (err.name !== 'AbortError') console.error(err);
});
// Cleanup: cancela request se userId mudar antes de resolver
return () => controller.abort();
}, [userId]); // ← primitivo estável
return <div>{user?.name}</div>;
}
// ✅ MELHOR AINDA: TanStack Query elimina o useEffect
function UserProfile({ userId }: { userId: string }) {
const { data: user } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId, { includeAvatar: true }),
});
return <div>{user?.name}</div>;
}Esse anti-pattern de useEffect é o bug mais frequente que vejo em code review. Na Agência Poti, adicionei uma regra de ESLint customizada que flagga useEffect com fetch dentro. Se o dev quer buscar dados, usa TanStack Query. Sem exceção. Para mais sobre armadilhas do JavaScript que causam esse tipo de bug, veja Lacunas Ocultas do JavaScript Avançado.
Anti-pattern 3: SSR para tudo
Página precisa de dados dinâmicos por request?
├── NÃO → SSG (Static Site Generation) ou ISR
│ ├── Conteúdo muda < 1x/hora → ISR com revalidate
│ └── Conteúdo estático → SSG puro
└── SIM
├── Dados são personalizados por usuário?
│ ├── SIM → SSR ou CSR com auth
│ └── NÃO → ISR com revalidate curto
└── Precisa de SEO?
├── SIM → SSR
└── NÃO → CSR (SPA) é suficienteEu vi um time usar SSR para um dashboard interno que só 12 pessoas acessavam. Cada page load batia no servidor, renderizava HTML, hidratava no client. O TTFB era 1.8s. Migrei para CSR puro com TanStack Query. TTFB caiu para 180ms (servindo um HTML estático) e os dados carregavam em 340ms via API. Para entender quando Server Components fazem sentido, veja Server Components no Next.js 16.
Anti-pattern 4: console.log como debug
// ❌ ERRADO: console.log espalhado pelo código
async function processOrder(order: Order) {
console.log('order', order);
console.log('processing...');
const result = await chargePayment(order);
console.log('result', result);
console.log('done');
return result;
}
// ✅ CORRETO: logging estruturado com níveis
import pino from 'pino';
const logger = pino({
level: process.env.LOG_LEVEL ?? 'info',
transport: process.env.NODE_ENV === 'development'
? { target: 'pino-pretty' }
: undefined,
});
async function processOrder(order: Order) {
logger.info({ orderId: order.id, total: order.total }, 'Iniciando processamento');
const result = await chargePayment(order);
if (result.success) {
logger.info({ orderId: order.id, chargeId: result.chargeId }, 'Pagamento aprovado');
} else {
logger.error({ orderId: order.id, error: result.error }, 'Pagamento recusado');
}
return result;
}Em produção, console.log é ruído. Não tem nível, não tem estrutura, não é pesquisável. Quando um bug acontece às 3h da manhã, você precisa de logs estruturados que o Datadog/Grafana/CloudWatch consegue filtrar. Escrevi sobre observabilidade em backend em Engenharia Backend com Node.js e TypeScript.
Anti-pattern 5: não configurar ESLint/Prettier no dia 1
O custo de adicionar linting em projeto existente cresce exponencialmente. Em projeto novo, leva 15 minutos. Em projeto com 6 meses de código sem padrão, leva 2-3 dias de fixes.
# Setup no dia 1 de qualquer projeto - 15 minutos
npm install -D eslint @eslint/js typescript-eslint prettier eslint-config-prettier
# eslint.config.ts (flat config, padrão desde ESLint 9)// eslint.config.ts
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import prettier from 'eslint-config-prettier';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.strictTypeChecked,
prettier,
{
rules: {
// Decisão: forçar retorno explícito em funções
'@typescript-eslint/explicit-function-return-type': 'error',
// Decisão: proibir any (usar unknown + type guard)
'@typescript-eslint/no-explicit-any': 'error',
// Decisão: proibir console.log (usar logger)
'no-console': ['error', { allow: ['warn', 'error'] }],
},
},
);Para mais sobre lacunas que devs ignoram em configuração de projetos, incluindo npm e segurança de dependências, tenho um artigo dedicado.
Roadmap progressivo: 5 níveis de maturidade
Esse roadmap não é linear. Você não precisa "completar" um nível para ir ao próximo. Mas cada nível pressupõe que o anterior está sólido.
Nível 1: Fundamentos (0-6 meses)
Foco: HTML semântico, CSS (Flexbox, Grid, Custom Properties), JavaScript (closures, promises, event loop).
Sem framework. Sem TypeScript. Sem build tool. Abra um arquivo .html, escreva JavaScript vanilla, e entenda o que o browser faz. Eu sei que é tentador pular para React no dia 1. Resista. Os devs que mais crescem rápido no meu time são os que entendem o DOM antes de abstraí-lo.
Recurso: CSS Custom Properties para entender variáveis nativas antes de usar Tailwind.
Nível 2: Ferramentas e tipagem (6-12 meses)
Foco: TypeScript (tipos utilitários, generics, discriminated unions), Git (rebase, cherry-pick, bisect), terminal, npm/pnpm.
TypeScript não é "JavaScript com tipos". É uma linguagem de modelagem que compila para JavaScript. A diferença importa. Quando você modela um domínio com discriminated unions, o compilador elimina categorias inteiras de bugs em compile time.
// Nível 2: modelar estados impossíveis como irrepresentáveis
// Isso não é "tipar por tipar". É eliminar bugs em compile time.
type AsyncState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
// O compilador GARANTE que você só acessa `data` quando status é 'success'
function renderState<T>(state: AsyncState<T>): string {
switch (state.status) {
case 'idle': return 'Aguardando...';
case 'loading': return 'Carregando...';
case 'success': return `Dados: ${JSON.stringify(state.data)}`;
case 'error': return `Erro: ${state.error.message}`;
}
}Aprofunde-se em Design Patterns que Todo Dev Senior Deveria Dominar em TypeScript.
Nível 3: Framework + ecossistema (12-24 meses)
Foco: Um framework (React, Vue ou Svelte), seu meta-framework (Next.js, Nuxt, SvelteKit), TanStack Query, Zustand/Pinia, Tailwind CSS, Vitest, Playwright.
Escolha UM framework usando o sistema de decisão da seção 2. Domine-o. Não fique pulando entre React e Vue a cada 3 meses. O State of JS confirma: 2.6 frameworks por carreira. Profundidade supera amplitude.
Nível 4: Arquitetura e backend (24-48 meses)
Foco: APIs REST e GraphQL, banco de dados (PostgreSQL), ORM (Prisma ou Drizzle), autenticação, rate limiting, connection pooling, arquitetura hexagonal.
Aqui você deixa de ser "dev frontend" e vira "dev web". Entender o backend muda como você projeta o frontend. Quando você sabe que a API tem rate limit de 100 req/s, você implementa debounce no client. Quando entende connection pooling, para de culpar o ORM pela lentidão.
Para comparar ORMs com benchmarks reais, veja Prisma ORM vs Drizzle ORM.
Nível 5: Escala e operação (48+ meses)
Foco: Docker, CI/CD, Kubernetes, observabilidade (logs, métricas, traces), feature flags, testes de carga, WebSockets vs SSE, Server Actions.
Neste nível, a pergunta muda de "como fazer funcionar" para "como fazer funcionar para 10.000 usuários simultâneos sem acordar às 3h da manhã". Observabilidade não é luxo. É o que separa "funciona no meu computador" de "funciona em produção".
Segurança, SEO técnico e observabilidade
Essas três áreas são ignoradas por devs júnior e cobradas por devs sênior em code review. Não são opcionais.
Segurança mínima viável
// Headers de segurança - configurar no middleware do Next.js ou no servidor
// next.config.ts
const securityHeaders = [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-XSS-Protection', value: '1; mode=block' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';",
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload',
},
];Além de headers, valide TODA entrada do usuário no servidor. Zod é a biblioteca padrão para isso em TypeScript:
import { z } from 'zod';
// Validação no servidor - nunca confie no client
const CreateUserSchema = z.object({
name: z.string().min(2).max(100).trim(),
email: z.string().email().toLowerCase(),
age: z.number().int().min(18).max(120),
});
// No handler da API
export async function POST(req: Request) {
const body = await req.json();
const parsed = CreateUserSchema.safeParse(body);
if (!parsed.success) {
return Response.json({ errors: parsed.error.flatten() }, { status: 400 });
}
// parsed.data é tipado e validado
const user = await db.user.create({ data: parsed.data });
return Response.json(user, { status: 201 });
}Aprofunde-se em 5 Lacunas Críticas que Todo Dev JavaScript Ignora em APIs, npm e Open Source.
SEO técnico para devs
SEO não é magia de marketing. É engenharia. Os fatores que você controla como dev:
- TTFB < 800ms: use SSG/ISR para páginas estáticas, edge functions para dinâmicas
- LCP < 2.5s: otimize imagens (next/image, sharp), code splitting, font-display: swap
- Meta tags dinâmicas: título único por página, description de 150-160 caracteres, Open Graph
- Sitemap e robots.txt: gerados automaticamente no build
- Dados estruturados: JSON-LD para artigos, produtos, FAQs
Observabilidade
A tríade é: logs (o que aconteceu), métricas (quanto aconteceu), traces (como aconteceu). Em frontend, a web-vitals lib reporta Core Web Vitals para seu backend de analytics. Em backend, Pino + OpenTelemetry cobrem logs e traces.
// Reportar Web Vitals para analytics
import { onCLS, onINP, onLCP, onFCP, onTTFB } from 'web-vitals';
function sendToAnalytics(metric: { name: string; value: number; id: string }) {
// Enviar para seu endpoint de analytics (não Google Analytics)
fetch('/api/vitals', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: metric.name,
value: Math.round(metric.value),
id: metric.id,
page: window.location.pathname,
timestamp: Date.now(),
}),
});
}
onCLS(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);
onFCP(sendToAnalytics);
onTTFB(sendToAnalytics);Cenários reais: qual stack para qual situação
Freelancer / dev solo
Stack: Next.js + Zustand + TanStack Query + Tailwind + shadcn/ui + Vitest
Deploy: Vercel (free tier → Pro quando faturar)
Motivo: maior mercado de vagas, mais tutoriais, deploy em 1 comando
Salário médio BR: R$8.000-14.000 (pleno), R$15.000-25.000 (sênior)Eu comecei como freelancer em 2005. Se tivesse que recomeçar hoje, iria de Next.js sem pensar duas vezes. O ecossistema é imbatível para quem trabalha sozinho.
Startup MVP (< 6 meses para mercado)
Stack: Vite + React + Zustand + TanStack Query + shadcn/ui + Supabase
Deploy: Vercel Pro + Supabase (Postgres gerenciado)
Motivo: velocidade de desenvolvimento máxima, custo baixo, fácil de contratar
Time ideal: 2-3 devs fullstackEm 2024, ajudei uma startup de saúde a ir do zero ao MVP em 11 semanas com essa stack. O custo de infra no primeiro mês foi R$0 (free tiers). No sexto mês, com 2.000 usuários ativos, o custo era R$340/mês.
Enterprise / banco / fintech regulada
Stack: Angular 18+ + RxJS + NgRx + Angular Material + Jasmine/Karma ou Jest
Deploy: AWS com VPC dedicada, ECS, CloudFront
Motivo: conventions fortes, tipagem rígida, compliance, equipe grande (15+ devs)
Salário médio BR: R$12.000-18.000 (pleno), R$20.000-35.000 (sênior)O moat do Angular em bancos brasileiros é real. Se você quer trabalhar no Itaú, Bradesco, ou TOTVS, Angular é o caminho. O ecossistema RxJS + NgRx é complexo, mas para apps com centenas de telas e dezenas de devs, a rigidez compensa.
Projeto content-first (blog, docs, marketing)
Stack: Astro + React islands (quando precisa de interatividade) + Tailwind
Deploy: Vercel ou Cloudflare Pages
Motivo: zero JS por default, 39 pontos acima de Next.js em satisfação
Performance: LCP < 1s em páginas estáticasAprendizado / projeto pessoal
Stack: SvelteKit + Tailwind + Vitest
Motivo: maior satisfação de devs (88%), sintaxe limpa, boa para aprender
Caveat: mercado BR ainda pequeno (~120 vagas), use para projetos pessoaisA frase que resume o sentimento da comunidade: "Devs get paid to write React but enjoy writing Svelte." Se você já domina React e quer expandir, SvelteKit é a escolha para projetos pessoais.
FAQ
Preciso trocar de framework a cada 2 anos?
Não. O State of JS 2025 mostra que a média é 2.6 frameworks por carreira inteira. Escolha um, domine, e só troque quando o mercado ou o projeto exigir. Framework hopping é a forma mais eficiente de nunca ficar sênior em nada.
TypeScript é obrigatório em 2026?
Sim. Todo projeto novo em 2026 que não usa TypeScript está acumulando dívida técnica desde o dia 1. Não é opinião. É o padrão da indústria. React, Vue, Svelte, Next.js, Astro, todos têm TypeScript como first-class citizen.
Redux ainda vale a pena?
Para projetos novos, não. TanStack Query resolve server state. Zustand resolve client state. Redux Toolkit ainda funciona em projetos existentes, mas o boilerplate não se justifica para projetos greenfield. Se você tem um projeto legado com Redux, migre incrementalmente: substitua os slices de server state por TanStack Query primeiro.
Webpack ou Vite para projeto novo?
Vite. Sem exceção. Cold start de 3s vs 45s. HMR de 45ms vs 2.1s. A única razão para manter Webpack é projeto legado com configuração complexa que custaria mais de 2 sprints para migrar.
Next.js ou Remix?
Next.js. O ecossistema é 5x maior, a Vercel investe pesado, e o mercado de vagas BR é incomparável. Remix tem boas ideias (loaders, actions), mas muitas delas foram absorvidas pelo Next.js App Router com Server Actions. Para entender Server Actions em profundidade, veja Revolucione sua Aplicação React com Next.js 15 Server Actions.
Devo aprender backend como dev frontend?
Sim. Entender o backend muda como você projeta o frontend. Saber que a API tem latência de 200ms muda como você implementa loading states. Saber que o banco tem connection pooling muda como você projeta queries. O guia de engenharia backend com Node.js e TypeScript é um bom ponto de partida.
Svelte é melhor que React?
Depende do critério. Satisfação do dev? Svelte ganha (88% vs 69%). Tamanho de mercado? React ganha por 30x. Bundle size em apps pequenos? Svelte ganha. Bundle size em apps com 19+ componentes? React empata ou ganha. A resposta correta é: Svelte é melhor para projetos pessoais e apps pequenos. React é melhor para empregabilidade e ecossistema.
Como organizar CSS em 2026?
Utility-first com Tailwind CSS é o padrão dominante. Se você vem de BEM ou OOCSS, a transição é estranha por 2 semanas e depois você não volta. Para entender as alternativas e quando cada uma faz sentido, escrevi uma comparação detalhada em BEM, OOCSS ou Utility-First.
Qual banco de dados para começar?
PostgreSQL. É o banco relacional mais completo, tem suporte a JSON (substitui MongoDB em 90% dos casos), e é o default de Supabase, Railway, Neon, e Vercel Postgres. Quando precisar escalar, connection pooling com PgBouncer resolve.
Jest ou Vitest?
Vitest para projeto novo. A API é praticamente idêntica ao Jest, o cold start é 5.6x mais rápido, e a integração com Vite elimina configuração de transformação. Para projetos existentes com Jest, migre quando tiver oportunidade. A migração é mecânica e leva menos de 1 dia para a maioria dos projetos.

Escrito por
Marcos Soares
Fullstack Developer · CEO da Agência Poti
Fullstack Developer e CEO da Agência Poti. Mais de 20 anos construindo arquiteturas cloud-native com React, Next.js e sistemas distribuídos. Parceiro comercial do estúdio iellou design. Fundador do Vivo de Código.
Comentários
Participe da discussão
Seja o primeiro a comentar!
Continue Aprofundando
Conteúdo técnico toda semana
Receba artigos sobre arquitetura, padrões de projeto e engenharia de software. Direto no seu e-mail, sem enrolação.
Sem spam. Cancele a qualquer momento com 1 clique.


