Como reduzi meu LCP em 70%
Aprenda métodos avançados para melhorar os Core Web Vitals

Melhorando as métricas de LCP com Web Workers e carregamento de imagens em 2 estágios
Na maioria das vezes, um elemento de imagem grande na viewport visível se tornará o elemento Largest Contentful Paint. Mesmo após aplicar todas as práticas recomendadas do Lighthouse, como redimensionamento de imagens, compressão de imagens, conversão para WebP e pré-carregamento do elemento LCP, o seu Largest Contentful Paint ainda pode não passar nos Core Web Vitals.
A única maneira de corrigir isso é usando táticas mais avançadas, como carregamento em 2 estágios e execução da sua página com Web Workers para liberar recursos na thread principal.

Última revisão por Arjen Karel em março de 2026
Um pouco de contexto
Sou um especialista em pagespeed e meu site é minha vitrine. Na minha página inicial, afirmo com orgulho que meu site é o mais rápido do mundo. É por isso que preciso que minha página carregue o mais rápido possível e que eu extraia cada gota de pagespeed do meu site.
As técnicas que mostrarei hoje podem não ser viáveis para um site comum (WordPress) sem o suporte de uma equipe de desenvolvimento dedicada e talentosa. Se você não puder replicar essa técnica no seu próprio site, ainda assim encorajo você a ler o artigo e aprender como eu penso sobre pagespeed e quais são as minhas considerações.
O problema: imagens grandes na viewport visível
Uma imagem grande na viewport visível frequentemente se tornará o elemento Largest Contentful Paint. Muitas vezes acontece de essa imagem LCP não passar nos Core Web Vitals. Vejo resultados assim diariamente.

Existem várias maneiras de garantir que este elemento apareça rapidamente na tela:
- Pré-carregue o elemento LCP. O pré-carregamento da imagem LCP garantirá que ela esteja disponível para o navegador o mais cedo possível. Combine isso com
fetchpriority="high"para dizer ao navegador para priorizar essa imagem sobre outros recursos. - Use imagens responsivas. Certifique-se de não estar servindo imagens no tamanho de desktop para dispositivos móveis.
- Comprima suas imagens. A compressão de imagens pode reduzir drasticamente o tamanho da imagem.
- Use formatos de imagem de última geração. Formatos de imagem de última geração, como WebP, superam formatos mais antigos, como JPEG e PNG, em quase todos os casos.
- Minimize o critical rendering path. Elimine todos os recursos de bloqueio de renderização, como JavaScript e folhas de estilo, que possam atrasar o LCP.
Infelizmente, apesar de todas essas otimizações, em alguns casos as métricas de LCP ainda podem não passar na auditoria dos Core Web Vitals. Por quê? O tamanho da imagem por si só é suficiente para atrasar a fase de resource load duration do LCP.
A solução: carregamento em 2 estágios e Web Workers
A solução que implementei (após otimizar todos os outros problemas no meu site) é o carregamento de imagens em 2 estágios.
A ideia é simples: na primeira renderização, mostre uma imagem de baixa qualidade com as mesmas dimensões exatas da imagem final de alta qualidade. Imediatamente após a exibição dessa imagem, inicie o processo que troca a imagem de baixa qualidade por uma imagem de alta qualidade.
Uma implementação muito básica seria algo assim: Primeiro, adicione um event listener de load a uma imagem. Quando a imagem carregar, esse mesmo event listener se desconecta e o src da imagem é trocado pela imagem final de alta qualidade.
<img
width="100"
height="100"
alt="some alt text"
src="lq.webp"
onload="this.onload=null;this.src='hq.webp'"
> Estágio 1: webp de baixa qualidade 3-5kb 
Estágio 2: webp de alta qualidade 20-40kb 
Isso pode parecer simples (e é), mas trocar um grande número de imagens logo no início do processo de renderização causará muita atividade na thread principal e afetará outras métricas dos Core Web Vitals.
É por isso que escolhi transferir parte do trabalho para um Web Worker. Um Web Worker roda em uma nova thread e não tem acesso real à página atual. A comunicação entre o Web Worker e a página é feita através de um sistema de mensagens. A vantagem óbvia é que não estamos usando a própria thread principal da página; estamos liberando recursos lá. A desvantagem é que usar um Web Worker pode ser um pouco trabalhoso.
O processo em si não é tão difícil. Uma vez que o evento DOMContentLoaded é disparado, eu coleto todas as imagens na página. Se uma imagem foi carregada, eu a trocarei imediatamente. Se não foi carregada (porque a imagem pode estar com lazy load), eu anexarei um event listener que troca a imagem após o lazy load.
Uma ressalva importante: o navegador trata cada troca de imagem como um novo candidato a LCP. Se a troca da imagem de alta qualidade acontecer após 2,5 segundos, o LCP será medido no tempo da troca, não no tempo do placeholder. É por isso que é importante que o Web Worker busque e troque a imagem o mais rápido possível.
O resultado: espetacular.

O código para carregamento do LCP em 2 estágios através de um Web Worker
Aqui está o código que uso para acelerar meu LCP através do carregamento em 2 estágios e um Web Worker. O código na página principal chama um Web Worker que buscará as imagens. O Web Worker passa o resultado como um blob para a página principal. Ao receber o blob, a imagem é trocada.
Worker.js
self.addEventListener('message', async event => {
const newimageURL = event.data.src.replace("/lq-","/resize-");
const response = await fetch(newimageURL)
const blob = await response.blob()
// Send the image data to the UI thread!
self.postMessage({
uid: event.data.uid,
blob: blob,
})
}) Script.js
O script.js rodará como um script normal na página da web ativa. O script primeiro carrega o worker. Em seguida, ele percorrerá todas as imagens de uma página. Isso acontece logo no início do processo de renderização. Uma imagem já pode estar carregada ou não. Se uma imagem de baixa qualidade já estiver carregada, ele chamará o processo de troca imediatamente. Se ainda não estiver carregada, ele anexará um listener ao evento de load da imagem que inicia o processo de troca assim que essa imagem for carregada.
Quando uma imagem é carregada, um id único é gerado para essa imagem. Isso me permite encontrar facilmente a imagem na página novamente (lembre-se, o worker não tem acesso ao DOM, então não posso enviar o Node DOM da imagem). A URL da imagem e o id único são então enviados para o worker. Quando o worker buscar a imagem, ela é enviada de volta ao script como um blob. O script eventualmente troca a antiga URL da imagem pela URL do blob que foi criada pelo Web Worker.
var myWorker = new Worker('/path-to/worker.js');
// send a message to worker
const sendMessage = (img) => {
// uid makes it easier to find the image
var uid = create_UID();
// set data-uid on image element
img.dataset.uid = uid;
// send message to worker
myWorker.postMessage({ src: img.src, uid: uid });
};
// generate the uid
const create_UID = () => {
var dt = new Date().getTime();
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (new Date().getTime() + Math.random() * 16) % 16 | 0;
dt = Math.floor(dt / 16);
return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
return uid;
}
// when we get a result from the worker
myWorker.addEventListener('message', event => {
// Grab the message data from the event
const imageData = event.data
// Get the original element for this image
const imageElement = document.querySelectorAll("img[data-uid='" + imageData.uid + "']");
// We can use the `Blob` as an image source! We just need to convert it
// to an object URL first
const objectURL = URL.createObjectURL(imageData.blob)
// Once the image is loaded, we'll want to do some extra cleanup
imageElement.onload = () => {
URL.revokeObjectURL(objectURL)
}
imageElement[0].setAttribute('src', objectURL)
})
// get all images
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll('img[loading="lazy"]').forEach(
img => {
// image is already visible?
img.complete ?
// swap immediately
sendMessage(img) :
// swap on load
img.addEventListener(
"load", i => { sendMessage(img) }, { once: true }
)
})
}) Para verificar a melhoria do LCP em campo, use Real User Monitoring para rastrear como seus visitantes reais vivenciam a página. Ferramentas de laboratório como o Lighthouse mostrarão a melhoria, mas são os dados de campo de usuários reais em conexões variadas que contam para passar nos Core Web Vitals.
CoreDash já vem com MCP.
Conecta no Claude ou em qualquer agente de IA. Pergunta pra ele por que seu INP disparou terça passada.
Vê como funciona
