Estava eu, totalmente imerso em código, subindo tool adoidado, para tentar bater uma meta pessoal de 100 tools no meu site Quick Easy, sem me dar conta que meu site poderia estar perdendo performance.
Fase 1: Code Splitting Granular com manualChunks
O Vite já faz code splitting automático, mas para dependências de terceiros ele tende a agrupar tudo em um único chunk vendor. A solução foi configurar manualChunks no vite.config.ts para separar cada biblioteca pesada no seu próprio arquivo.
O resultado: cada ferramenta agora carrega apenas os chunks que realmente precisa. Um usuário que acessa o conversor de texto não baixa nada do highlight.js. Quem usa o JSON validator não toca no qr-code-styling. Além do benefício de tamanho, chunks nomeados melhoram muito o cache invalidation: quando atualizamos a lógica do JSON validator, apenas o chunk vendor-ajv e o chunk da ferramenta são invalidados, o resto continua em cache.
Fase 2: Dynamic Imports onde importa
Só separar os chunks não é suficiente se o import ainda é estático. A biblioteca marked estava sendo importada com import { marked } from 'marked' diretamente no +page.ts, o que forçava o browser a baixá-la antes de qualquer renderização.
A correção foi converter para dynamic import:
Com isso, o vendor-markdown chunk só é requisitado quando o usuário abre uma ferramenta que precisa renderizar Markdown e mesmo assim, de forma assíncrona, sem bloquear o restante da página.
Nota importante para quem usa SvelteKit com adapter-static: o load em +page.ts só executa no build time com SSG. No cliente, o SvelteKit usa o __data.json pré-renderizado e nunca re-executa o load no browser. Isso significa que dynamic imports dentro do load function não têm custo no bundle client-side o output do load já está serializado no JSON.
Fase 3: Service Worker com cache-first para assets
O último passo foi adicionar um Service Worker para maximizar o uso de cache entre sessões: a estratégia é diferente por tipo de recurso.
Assets com hash (os chunks do Vite): cache-first. Um arquivo vendor-highlight-abc123.js nunca muda, se está em cache, serve do cache direto, sem nem tocar na rede.
Páginas HTML e dados: network-first, com fallback para cache quando offline.
Isso significa que na segunda visita, praticamente todo o JS e CSS já está no cache do browser, o site carrega como se fosse local.
Fase 4: Animações sem forçar reflow
Uma causa comum de TBT alto é CSS que força o browser a recalcular layout durante animações. Encontramos alguns lugares usando transition-all, que inclui propriedades como height, width e top, todas elas trigam layout reflow.
A correção foi substituir por propriedades que só afetam compositing: opacity e transform são tratados diretamente pela GPU, sem passar pelo main thread de layout. No Sonner (biblioteca de toasts), removemos a animação de height que estava causando um reflow visível a cada notificação.
Conclusão
Depois de passar por vários bugs, e dor de cabeça, mais fases do que está aqui escrito, e vários rounds, consegui, com muita ajuda das IAs, ajustar o site enfim. Agora o site bate 90-95 pontos no lighthouse. Mas o que aprendi é que tem que ser uma monitoria constante.
Bom, um pouco de mercha para quem não conhece o Quick Easy (ninguem conhece 😢), a menos de 60 dias eu resolvi criar um site de tools, onde eu sou o principal usuário do site. Hoje, com certeza eu já subi mais tools do que eu consigo usar. Mas a maioria delas eu mesmo utilizo, para quem quer conhecer melhor, já temos várias tools úteis, e algumas inuteis como o gerador de código morse.
Mas algumas tools são: Gerador e validador de CNPJ, já com o novo padrão que entra em junho desse ano Gerador de CPF Comprimir imagens Não tem tool só pra dev não, tem pra designer, financeiro, etc. Uma outra que utilizo bastante é a calculadora de juros compostos