Como eu 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 imagem em 2 etapas
Na maioria das vezes, um elemento de imagem grande no viewport visível se tornará o elemento Largest Contentful Paint. Mesmo depois de aplicar todas as melhores práticas do Lighthouse, como redimensionamento de imagem, compressão de imagem, conversão para WebP e preloading do elemento LCP, 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 o carregamento em 2 etapas e o threading da sua página com Web Workers para liberar recursos na main thread.

Revisado pela última vez por Arjen Karel em março de 2026
Um pouco de contexto
Eu sou um cara fissurado por pagespeed e meu site é minha vitrine. Na minha homepage, afirmo orgulhosamente 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 esprema cada gota de pagespeed do meu site.
As técnicas que mostrarei hoje podem não ser viáveis para o site médio (WordPress) sem o apoio de uma equipe de desenvolvimento dedicada e talentosa. Se você não puder duplicar esta técnica em seu próprio site, ainda assim o encorajo a ler o artigo e aprender como eu penso sobre pagespeed e quais são minhas considerações.
O problema: imagens grandes no viewport visível
Uma imagem grande no viewport visível frequentemente se tornará o elemento Largest Contentful Paint. Acontece frequentemente que esta imagem LCP não passa nos Core Web Vitals. Vejo resultados como este diariamente.

Existem várias maneiras de garantir que este elemento apareça na tela rapidamente:
- Fazer o preload do elemento LCP. O preloading da imagem LCP garantirá que esta imagem esteja disponível para o navegador o mais cedo possível. Combine isso com
fetchpriority="high"para dizer ao navegador para priorizar esta imagem sobre outros recursos. - Usar imagens responsivas. Certifique-se de que não está servindo imagens de tamanho desktop para dispositivos móveis.
- Comprimir suas imagens. A compressão de imagem pode reduzir drasticamente o tamanho da imagem.
- Usar formatos de imagem de última geração. Formatos de imagem de última geração como WebP superam formatos antigos como JPEG e PNG em quase todos os casos.
- Minimizar o critical rendering path. Elimine todos os recursos que bloqueiam a renderização, como JavaScript e folhas de estilo, que podem atrasar o LCP.
Infelizmente, apesar de todas estas 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 etapas e Web Workers
A solução que implementei (depois de otimizar todos os outros problemas no meu site) é o carregamento de imagem em 2 etapas.
A ideia é simples: no primeiro render, 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 pode ser algo assim: Primeiro, adicione um event listener de carregamento a uma imagem. Quando a imagem carrega, esse mesmo event listener se desconecta e o src da imagem é trocado pela imagem final de alta qualidade.
<img
width="100"
height="100"
alt="algum texto alt"
src="lq.webp"
onload="this.onload=null;this.src='hq.webp'"
>
Etapa 1: webp de baixa qualidade 3-5kb

Etapa 2: webp de alta qualidade 20-40kb

Isto pode parecer simples o suficiente (e é), mas trocar um grande número de imagens no início do processo de renderização causará muita atividade na main thread e afetará outras métricas dos Core Web Vitals.
É por isso que escolhi descarregar 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 main thread 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 foi disparado, eu coleto todas as imagens na página. Se uma imagem já tiver sido carregada, eu a trocarei imediatamente. Se não tiver sido carregada (porque a imagem pode estar em lazy load), eu anexarei um event listener que troca a imagem após o lazy load.
Um detalhe importante: o navegador trata cada troca de imagem como um novo candidato a LCP. Se a troca da sua imagem de alta qualidade acontecer após 2,5 segundos, o LCP será medido no momento da troca, não no momento do placeholder. Por isso, é importante que o Web Worker busque e troque a imagem o mais rápido possível.
O resultado: espetacular.

O código para carregamento de LCP em 2 etapas via Web Worker
Aqui está o código que uso para acelerar meu LCP através do carregamento em 2 etapas 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 la 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()
// Envia os dados da imagem para a thread de UI!
self.postMessage({
uid: event.data.uid,
blob: blob,
})
})
Script.js
O script.js rodará como um script normal na página web ativa. O script primeiro carrega o worker. Em seguida, ele percorrerá todas as imagens em uma página. Isso acontece no início do processo de renderização. Uma imagem pode já 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 carga da imagem que inicia o processo de troca assim que a 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 nó DOM da imagem). A URL da imagem e o id único são então enviados para o worker. Quando o worker buscou a imagem, ela é enviada de volta para o script como um blob. O script eventualmente troca a URL da imagem antiga pela URL do blob que foi criada pelo Web Worker.
var myWorker = new Worker('/path-to/worker.js');
// envia uma mensagem para o worker
const sendMessage = (img) => {
// uid facilita encontrar a imagem
var uid = create_UID();
// define data-uid no elemento de imagem
img.dataset.uid = uid;
// envia mensagem para o worker
myWorker.postMessage({ src: img.src, uid: uid });
};
// gera o 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;
}
// quando recebemos um resultado do worker
myWorker.addEventListener('message', event => {
// Pega os dados da imagem do evento
const imageData = event.data
// Obtém o elemento original para esta imagem
const imageElement = document.querySelectorAll("img[data-uid='" + imageData.uid + "']");
// Podemos usar o `Blob` como fonte de imagem! Só precisamos convertê-lo
// para uma URL de objeto primeiro
const objectURL = URL.createObjectURL(imageData.blob)
// Assim que a imagem carregar, vamos querer fazer uma limpeza extra
imageElement.onload = () => {
URL.revokeObjectURL(objectURL)
}
imageElement[0].setAttribute('src', objectURL)
})
// pega todas as imagens
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll('img[loading="lazy"]').forEach(
img => {
// imagem já está visível?
img.complete ?
// troca imediatamente
sendMessage(img) :
// troca ao carregar
img.addEventListener(
"load", i => { sendMessage(img) }, { once: true }
)
})
})
Para verificar a melhoria de LCP no campo, use Real User Monitoring para acompanhar como seus visitantes reais experimentam a página. Ferramentas de laboratório como o Lighthouse mostrarão a melhoria, mas os dados de campo de usuários reais em conexões variadas são o que conta para passar nos Core Web Vitals.
17 years of fixing PageSpeed.
I have optimized platforms for some of the largest publishers and e-commerce sites in Europe. I provide the strategy, the code, and the RUM verification. Usually in 1 to 2 sprints.
View Services
