JavaScript Minimalista: Como Vencer o Framework Fatigue e Melhorar Performance em 2025
Olá HaWkers, você já se sentiu exausto com a quantidade absurda de frameworks, bibliotecas e ferramentas no ecossistema JavaScript? Não está sozinho. O framework fatigue é real, e uma nova tendência está surgindo em 2025: JavaScript minimalista.
Desenvolvedores e empresas estão percebendo que menos código = melhor performance = melhor SEO = mais dinheiro. Vamos explorar como simplificar seu stack sem perder produtividade.
O Problema: Bloated Code Está Matando Performance
Realidade em 2025: Bundle Size Out of Control
// Projeto JavaScript típico em 2023-2024
const typicalProject2024 = {
react: "18.2.0", // 44kb gzipped
reactDom: "18.2.0", // 132kb gzipped
redux: "4.2.1", // 3kb
reactRedux: "8.1.3", // 16kb
reduxToolkit: "1.9.7", // 48kb
reactRouter: "6.20.1", // 14kb
axios: "1.6.2", // 13kb
lodash: "4.17.21", // 24kb (se importar tudo)
momentJs: "2.29.4", // 72kb (!) - deprecated mas ainda usado
materialUI: "5.14.20", // 400kb+ (!)
totalBundle: "~800kb gzipped", // 2.5-3MB uncompressed
loadTime: "4-7s on 3G",
lighthouseScore: "45-65 (poor)",
seoImpact: "Penalizado pelo Google (Core Web Vitals)",
};
// Projeto minimalista em 2025
const minimalistProject2025 = {
react: "18.3.0", // Still 44kb (necessário)
reactDom: "18.3.0", // Still 132kb (necessário)
zustand: "4.5.0", // 1.2kb (!!) - substitui Redux
tinyRouter: "Custom", // 2kb - substitui React Router
nativeFetch: "Built-in", // 0kb - substitui Axios
nativeMethods: "Built-in", // 0kb - substitui Lodash
dayjs: "1.11.10", // 2kb - substitui Moment.js
tailwindCSS: "3.4.0", // 10kb runtime - substitui MUI
totalBundle: "~200kb gzipped", // 600kb uncompressed
loadTime: "1-2s on 3G",
lighthouseScore: "90-100 (excellent)",
seoImpact: "Favorecido pelo Google",
savings: {
bundleSize: "-75%",
loadTime: "-60%",
maintenanceCost: "-50%",
},
};
Vanilla JS Renaissance: O Retorno aos Fundamentos
Quando Vanilla JS é Suficiente
// 1. Manipulação de DOM (não precisa de jQuery em 2025)
// ❌ Antes (jQuery): 30kb de overhead
$("#myButton").on("click", function () {
$(this).addClass("active");
$(".modal").fadeIn();
});
// ✅ Agora (Vanilla JS): 0kb overhead
document.getElementById("myButton").addEventListener("click", function () {
this.classList.add("active");
// Fade in com CSS + JS
const modal = document.querySelector(".modal");
modal.style.display = "block";
modal.style.opacity = "0";
requestAnimationFrame(() => {
modal.style.transition = "opacity 0.3s";
modal.style.opacity = "1";
});
});
// 2. HTTP requests (Fetch API nativa)
// ❌ Antes (Axios): 13kb
import axios from "axios";
const { data } = await axios.get("/api/users", {
params: { role: "admin" },
timeout: 5000,
});
// ✅ Agora (Fetch nativo): 0kb
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
const response = await fetch("/api/users?role=admin", {
signal: controller.signal,
});
clearTimeout(timeout);
const data = await response.json();
// Wrapper reutilizável
async function fetchWithTimeout(url, options = {}, timeoutMs = 5000) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, {
...options,
signal: controller.signal,
});
clearTimeout(timeout);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
if (error.name === "AbortError") {
throw new Error("Request timeout");
}
throw error;
}
}
// 3. Array/Object manipulation (sem Lodash)
// ❌ Antes (Lodash): 24kb
import _ from "lodash";
const uniqueUsers = _.uniqBy(users, "id");
const grouped = _.groupBy(products, "category");
const debounced = _.debounce(handleSearch, 300);
// ✅ Agora (Native JS): 0kb
const uniqueUsers = [...new Map(users.map((u) => [u.id, u])).values()];
const grouped = products.reduce((acc, product) => {
const category = product.category;
if (!acc[category]) acc[category] = [];
acc[category].push(product);
return acc;
}, {});
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), wait);
};
}
const debouncedSearch = debounce(handleSearch, 300);Web Components: Futuro do JavaScript Minimalista?
// Web Components nativos (zero dependencies)
class TodoList extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.todos = [];
}
connectedCallback() {
this.render();
this.attachEventListeners();
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
font-family: system-ui;
}
.todo-item {
display: flex;
gap: 12px;
padding: 12px;
border-bottom: 1px solid #eee;
}
.todo-item.completed {
opacity: 0.5;
text-decoration: line-through;
}
button {
background: #007bff;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #0056b3;
}
</style>
<div class="todo-app">
<input
type="text"
id="newTodo"
placeholder="Add new todo..."
/>
<button id="addBtn">Add</button>
<div id="todoList">
${this.todos
.map(
(todo) => `
<div class="todo-item ${todo.completed ? "completed" : ""}" data-id="${todo.id}">
<input type="checkbox" ${todo.completed ? "checked" : ""} />
<span>${todo.text}</span>
<button class="delete-btn">Delete</button>
</div>
`
)
.join("")}
</div>
</div>
`;
}
attachEventListeners() {
const addBtn = this.shadowRoot.getElementById("addBtn");
const input = this.shadowRoot.getElementById("newTodo");
addBtn.addEventListener("click", () => {
const text = input.value.trim();
if (text) {
this.addTodo(text);
input.value = "";
}
});
// Event delegation para checkboxes e delete buttons
this.shadowRoot.addEventListener("change", (e) => {
if (e.target.type === "checkbox") {
const id = e.target.closest(".todo-item").dataset.id;
this.toggleTodo(id);
}
});
this.shadowRoot.addEventListener("click", (e) => {
if (e.target.classList.contains("delete-btn")) {
const id = e.target.closest(".todo-item").dataset.id;
this.deleteTodo(id);
}
});
}
addTodo(text) {
this.todos.push({
id: Date.now().toString(),
text,
completed: false,
});
this.render();
this.attachEventListeners();
}
toggleTodo(id) {
const todo = this.todos.find((t) => t.id === id);
if (todo) {
todo.completed = !todo.completed;
this.render();
this.attachEventListeners();
}
}
deleteTodo(id) {
this.todos = this.todos.filter((t) => t.id !== id);
this.render();
this.attachEventListeners();
}
}
// Registrar componente
customElements.define("todo-list", TodoList);
// Uso em HTML (zero build step!)
// <todo-list></todo-list>
// Bundle size: ~0kb (nativo do browser)
// Compatibilidade: Todos os browsers modernos (98%+ suporte)
Zustand vs Redux: A Revolução Minimalista
Por Que Zustand Está Dominando em 2025
// ❌ Redux Toolkit (ainda verboso mesmo "simplificado")
// Arquivos: store.ts, userSlice.ts, productSlice.ts, hooks.ts, etc.
// store.ts
import { configureStore } from "@reduxjs/toolkit";
import userReducer from "./userSlice";
import productReducer from "./productSlice";
export const store = configureStore({
reducer: {
user: userReducer,
products: productReducer,
},
});
// userSlice.ts
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
export const fetchUser = createAsyncThunk("user/fetchUser", async (id) => {
const response = await fetch(`/api/users/${id}`);
return response.json();
});
const userSlice = createSlice({
name: "user",
initialState: { data: null, loading: false, error: null },
reducers: {
setUser: (state, action) => {
state.data = action.payload;
},
},
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => {
state.loading = true;
})
.addCase(fetchUser.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(fetchUser.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
});
},
});
export const { setUser } = userSlice.actions;
export default userSlice.reducer;
// Component
import { useDispatch, useSelector } from "react-redux";
function UserProfile() {
const dispatch = useDispatch();
const user = useSelector((state) => state.user.data);
const loading = useSelector((state) => state.user.loading);
useEffect(() => {
dispatch(fetchUser("123"));
}, []);
// ...
}
// Total: ~100 linhas de código, múltiplos arquivos, 67kb bundle
// ✅ Zustand (minimalista e poderoso)
import { create } from "zustand";
interface UserState {
user: User | null;
loading: boolean;
error: string | null;
fetchUser: (id: string) => Promise<void>;
setUser: (user: User) => void;
}
export const useUserStore = create<UserState>((set) => ({
user: null,
loading: false,
error: null,
fetchUser: async (id) => {
set({ loading: true, error: null });
try {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
set({ user, loading: false });
} catch (error) {
set({ error: error.message, loading: false });
}
},
setUser: (user) => set({ user }),
}));
// Component (MUITO mais simples)
function UserProfile() {
const { user, loading, fetchUser } = useUserStore();
useEffect(() => {
fetchUser("123");
}, []);
if (loading) return <Spinner />;
return <div>{user?.name}</div>;
}
// Total: ~30 linhas, 1 arquivo, 1.2kb bundle (-98%!)Zustand: Casos de Uso Avançados
// Persistência automática
import { create } from "zustand";
import { persist } from "zustand/middleware";
export const useCartStore = create(
persist(
(set, get) => ({
items: [],
total: 0,
addItem: (item) =>
set((state) => {
const existing = state.items.find((i) => i.id === item.id);
if (existing) {
return {
items: state.items.map((i) =>
i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
),
};
}
return { items: [...state.items, { ...item, quantity: 1 }] };
}),
removeItem: (id) =>
set((state) => ({
items: state.items.filter((i) => i.id !== id),
})),
// Computed value (similar a getters)
get total() {
return get().items.reduce((sum, item) => sum + item.price * item.quantity, 0);
},
}),
{
name: "cart-storage", // localStorage key
partialize: (state) => ({ items: state.items }), // Salva apenas items
}
)
);
// Slices pattern (organização avançada)
const createUserSlice = (set) => ({
user: null,
login: (credentials) => {
/* ... */
},
logout: () => set({ user: null }),
});
const createProductSlice = (set) => ({
products: [],
fetchProducts: async () => {
/* ... */
},
});
export const useStore = create((...a) => ({
...createUserSlice(...a),
...createProductSlice(...a),
}));
// Middleware customizado (DevTools, logging, etc)
import { devtools, subscribeWithSelector } from "zustand/middleware";
export const useStore = create(
devtools(
subscribeWithSelector((set) => ({
// State aqui
})),
{ name: "MyAppStore" }
)
);
// Subscribe a mudanças específicas
useStore.subscribe(
(state) => state.user,
(user, prevUser) => {
console.log("User changed:", { user, prevUser });
// Analytics, side effects, etc
}
);
Jotai: O Minimalista Atômico
// Jotai: Ainda mais minimalista que Zustand (atoms approach)
import { atom, useAtom } from "jotai";
// 1. Atoms básicos
const countAtom = atom(0);
const userAtom = atom(null);
// Component
function Counter() {
const [count, setCount] = useAtom(countAtom);
return (
<button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>
);
}
// 2. Derived atoms (computed values)
const todosAtom = atom([]);
const completedTodosAtom = atom((get) =>
get(todosAtom).filter((t) => t.completed)
);
const todoStatsAtom = atom((get) => {
const todos = get(todosAtom);
return {
total: todos.length,
completed: get(completedTodosAtom).length,
pending: todos.filter((t) => !t.completed).length,
};
});
// Component
function TodoStats() {
const [stats] = useAtom(todoStatsAtom);
return (
<div>
Total: {stats.total} | Completed: {stats.completed} | Pending:{" "}
{stats.pending}
</div>
);
}
// 3. Async atoms
const userIdAtom = atom("123");
const userAtom = atom(async (get) => {
const userId = get(userIdAtom);
const response = await fetch(`/api/users/${userId}`);
return response.json();
});
// Component (Suspense automático!)
function UserProfile() {
const [user] = useAtom(userAtom);
// user é Promise, mas Suspense handle isso
return <div>{user.name}</div>;
}
// Wrapper com Suspense
function App() {
return (
<Suspense fallback={<Spinner />}>
<UserProfile />
</Suspense>
);
}
// 4. Write-only atoms (actions)
const todosAtom = atom([]);
const addTodoAtom = atom(
null, // read function (null = write-only)
(get, set, newTodo) => {
set(todosAtom, [...get(todosAtom), newTodo]);
}
);
const removeTodoAtom = atom(null, (get, set, id) => {
set(
todosAtom,
get(todosAtom).filter((t) => t.id !== id)
);
});
// Component
function AddTodo() {
const [, addTodo] = useAtom(addTodoAtom);
return (
<button onClick={() => addTodo({ id: Date.now(), text: "New todo" })}>
Add
</button>
);
}
// Bundle: 3kb (!!!) - Menor que Zustand
// Performance: Excelente (re-renders apenas quando atom específico muda)Zustand vs Jotai: Quando Usar Cada Um
const stateManagementGuide = {
useZustand: {
when: [
"Store global centralizado",
"Muitas actions complexas",
"Familiaridade com Redux",
"Persistência localStorage",
"DevTools essencial",
],
examples: ["E-commerce (cart)", "Auth state", "Theme/settings"],
size: "1.2kb",
},
useJotai: {
when: [
"State granular (muitos atoms pequenos)",
"Derived state complexo",
"Suspense/async nativo",
"Performance crítica (evitar re-renders)",
"Bundle size crítico",
],
examples: ["Formulários complexos", "Real-time dashboards", "Game state"],
size: "3kb",
},
useReact: {
when: ["State local simples", "Componente isolado", "Protótipos rápidos"],
examples: ["Toggles", "Form inputs", "Modals"],
size: "0kb (built-in)",
},
};
Performance: Métricas Reais
// Benchmark: Aplicação e-commerce (10k produtos)
const performanceComparison = {
heavyStack: {
// React + Redux Toolkit + MUI + Lodash + Moment
bundleSize: "850kb gzipped",
fcp: "2.8s", // First Contentful Paint
lcp: "4.5s", // Largest Contentful Paint
tti: "5.2s", // Time to Interactive
tbt: "890ms", // Total Blocking Time
cls: "0.15", // Cumulative Layout Shift
lighthouse: "58/100",
seoImpact: "Penalizado (-15% tráfego orgânico)",
},
minimalistStack: {
// React + Zustand + Tailwind + Native JS + day.js
bundleSize: "220kb gzipped",
fcp: "0.9s",
lcp: "1.4s",
tti: "1.8s",
tbt: "120ms",
cls: "0.02",
lighthouse: "94/100",
seoImpact: "Favorecido (+18% tráfego orgânico)",
},
improvements: {
bundleSize: "-74%",
lcp: "-68%",
tti: "-65%",
tbt: "-86%",
lighthouse: "+62%",
seoTraffic: "+33%",
},
businessImpact: {
conversionRate: "+12% (faster load = more sales)",
bounceRate: "-28%",
seoRevenue: "+$45k/month (small e-commerce)",
},
};Estratégias de Dependency Reduction
// Auditoria de dependencies
// 1. Analisar bundle atual
// npx webpack-bundle-analyzer dist/stats.json
// 2. Encontrar substitutos menores
const replacements = {
moment: "day.js", // 72kb → 2kb (-97%)
lodash: "native JS", // 24kb → 0kb (-100%)
axios: "native fetch", // 13kb → 0kb (-100%)
uuid: "crypto.randomUUID()", // 4kb → 0kb (-100%)
classnames: "clsx", // 1.5kb → 0.5kb (-66%)
redux: "zustand", // 67kb → 1.2kb (-98%)
};
// 3. Tree-shaking correto
// ❌ Importa tudo
import _ from "lodash";
import * as MUI from "@mui/material";
// ✅ Importa apenas necessário
import debounce from "lodash/debounce";
import { Button, TextField } from "@mui/material";
// 4. Lazy loading
// ❌ Carrega tudo upfront
import Dashboard from "./Dashboard";
import Settings from "./Settings";
import Analytics from "./Analytics";
// ✅ Carrega sob demanda
const Dashboard = lazy(() => import("./Dashboard"));
const Settings = lazy(() => import("./Settings"));
const Analytics = lazy(() => import("./Analytics"));
// 5. Remover código morto
// npx depcheck (mostra deps não usadas)
// npx unimported (mostra imports não usados)
// 6. CDN para libs específicas
// Exemplo: Chart.js (só usado em 1 página)
// <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
// Não inclui no bundle principalConclusão: Less is More em 2025
O JavaScript minimalista não é regredir — é evoluir para soluções mais eficientes.
Realidade comprovada:
const minimalistBenefits = {
performance: "+60-80% faster load times",
seo: "+15-30% tráfego orgânico",
maintenance: "-50% tempo debugando dependencies",
development: "Feature velocity igual ou maior (IA compensa)",
costs: "-30% custos de infraestrutura (menos CDN, menos compute)",
};
const actionPlan = {
immediate: [
"Audit dependencies (npx depcheck)",
"Substituir Lodash por native JS",
"Considerar Zustand se usa Redux",
"Lazy load rotas pesadas",
],
shortTerm: [
"Refactor para Vanilla JS onde faz sentido",
"Avaliar Jotai para state granular",
"Implementar code splitting agressivo",
],
longTerm: ["Web Components para design system", "Zero-dependency culture"],
};Menos código, mais resultado.
Se você quer entender mais sobre performance web moderna, recomendo: WebAssembly + JavaScript Performance.
Bora pra cima! 🦅
📚 Quer Dominar JavaScript Moderno?
Este artigo mostrou técnicas minimalistas, mas dominar JavaScript sólido é fundamental para aplicá-las corretamente.
Material de Estudo Completo
Preparei um guia completo de JavaScript do básico ao avançado com foco em performance e boas práticas:
Opções de investimento:
- R$9,90 (pagamento único)
💡 Fundamentos sólidos para construir aplicações minimalistas e performáticas

