Segundo os últimos dados da ACEPI (Associação da Economia Digital), 73% das compras online em Portugal são iniciadas no telemóvel e mais de metade são concluídas no mesmo dispositivo. Esta realidade mudou por completo o jogo da performance: o que outrora se media em segundos de carregamento, mede-se hoje em milissegundos de reatividade. O INP (Interaction to Next Paint), oficialmente substituto do FID desde Março de 2024, tornou-se o verdadeiro assassino silencioso dos Core Web Vitals em 2026 — e a maioria dos sites portugueses ainda não percebeu o impacto que isto tem nas conversões.
Quando analisamos o CrUX Report dos principais players nacionais — Worten, Continente Online, FNAC PT, Sport Zone, La Redoute PT — encontramos um padrão claro: o LCP foi razoavelmente domado, mas o INP em telemóveis de gama média (que representam o grosso do parque instalado em Portugal) continua a falhar o limiar dos 200 ms em mais de 40% das sessões. Este artigo é um guia técnico, sem rodeios, para resolver os três Core Web Vitals que importam em 2026, com snippets reais para Next.js 15 e RSC (React Server Components), e com referências concretas ao mercado português.
1. Medir e corrigir o INP no e-commerce português
O INP mede a latência da pior interação do utilizador durante toda a sessão — clique, toque ou tecla. O limiar verde é ≤ 200 ms, o laranja vai até 500 ms, e tudo o que ultrapasse isso é vermelho. Em e-commerce português, as interações mais problemáticas são tipicamente: adicionar ao carrinho, abrir o mega-menu de categorias, filtrar uma listagem (PLP) e abrir a galeria de imagens do produto (PDP).
Para medir o INP em produção, a abordagem correcta é instalar a biblioteca `web-vitals` directamente no `app/layout.tsx` e enviar os dados para um endpoint próprio (evitando o GA4, que amostra mal o INP em sessões longas):
// app/components/WebVitalsReporter.tsx
"use client";
import { useEffect } from "react";
import { onINP, onLCP, onCLS } from "web-vitals/attribution";
export function WebVitalsReporter() {
useEffect(() => {
const send = (metric: any) => {
navigator.sendBeacon(
"/api/vitals",
JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating,
target: metric.attribution?.interactionTarget,
url: window.location.pathname,
})
);
};
onINP(send, { reportAllChanges: false });
onLCP(send);
onCLS(send);
}, []);
return null;
}
A propriedade `interactionTarget` do objecto de atribuição é ouro puro: indica exactamente qual o elemento DOM responsável pela pior interação. Em três sites portugueses que auditámos no último trimestre, o vilão era sempre o mesmo — um handler `onClick` síncrono no botão "Adicionar ao carrinho" que despoletava atualização do estado global, chamada à API e re-render de toda a árvore de componentes em simultâneo.
A correção típica passa por três técnicas combinadas. Primeiro, usar `startTransition` do React para marcar actualizações não urgentes como concorrentes:
import { startTransition, useTransition } from "react";
function AddToCartButton({ productId }: { productId: string }) {
const [isPending, startTransition] = useTransition();
const handleClick = () => {
// Feedback visual imediato (urgente)
setOptimisticCount((c) => c + 1);
// Pesado, mas não bloqueante
startTransition(() => {
addToCart(productId);
revalidateCartBadge();
});
};
return (
<button onClick={handleClick} disabled={isPending}>
Adicionar ao carrinho
</button>
);
}
Segundo, usar `useDeferredValue` para componentes pesados (mega-menu, filtros facetados). Terceiro, fragmentar tarefas longas com `scheduler.yield()` (já disponível em todos os Chromium-based desde Outubro 2025): em vez de um `for` que processa 500 produtos de uma vez, dividir em blocos de 50 e ceder controlo ao browser entre cada bloco. Esta técnica sozinha baixou o INP de 380 ms para 145 ms num cliente nosso do sector da moda.
2. Optimizar o LCP: Next.js Image, AVIF e priority hints
O LCP em e-commerce português é quase sempre a imagem principal do produto (PDP) ou o hero banner (homepage). O limiar verde mantém-se nos 2,5 s, mas o objetivo realista em 2026, com a infra-estrutura disponível, deve ser ≤ 1,8 s para se destacar da concorrência. Os players nacionais mais rápidos já navegam consistentemente abaixo dos 1,5 s na PDP graças a uma combinação de AVIF, CDN europeu e preload estratégico.
Com o Next.js 15 e o componente `<Image>` actualizado, a configuração correcta para uma imagem LCP é a seguinte:
// app/produto/[slug]/page.tsx
import Image from "next/image";
export default async function ProductPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const product = await getProduct(slug);
return (
<Image
src={product.heroImage}
alt={product.name}
width={800}
height={800}
priority
fetchPriority="high"
sizes="(max-width: 768px) 100vw, 50vw"
quality={80}
formats={["image/avif", "image/webp"]}
/>
);
}
Pontos críticos: `priority` injecta automaticamente um `<link rel="preload">` no `<head>`; `fetchPriority="high"` força o browser a colocar este recurso no topo da fila de descodificação; `sizes` permite ao Next.js servir o tamanho exacto necessário para cada viewport, poupando até 70% de bytes em telemóveis. O AVIF tipicamente reduz o peso em 35-50% face ao WebP, e a partir do iOS 17 já tem suporte universal em Portugal (98,3% segundo o caniuse com filtro PT).
No `next.config.ts`, configurar o domain remote e o cache imutável:
// next.config.ts
import type { NextConfig } from "next";
const config: NextConfig = {
images: {
formats: ["image/avif", "image/webp"],
minimumCacheTTL: 31536000, // 1 ano
remotePatterns: [
{ protocol: "https", hostname: "cdn.exemplo.pt" },
],
},
};
export default config;
Para o hero da homepage, recomendamos ainda servir uma variante específica para conexões 4G/5G via `Network Information API`, ou no mínimo um `blurDataURL` baseado em LQIP (Low Quality Image Placeholder) de ~20 bytes. Um cliente nosso de equipamento desportivo em Aveiro reduziu o LCP em telemóvel de 3,2 s para 1,7 s só com esta combinação.
3. Prevenir o CLS: carregamento de fontes e conteúdo dinâmico
O CLS (Cumulative Layout Shift) deve manter-se ≤ 0,1. Os dois grandes culpados em e-commerce português são: o flash de fonte sem estilo (FOUT) quando se carrega uma webfont da Google Fonts, e a injeção de banners (cookies, promoções, RGPD) sem reserva de espaço. A solução para fontes em Next.js 15 passa pelo `next/font`, que faz auto-hosting e gera `size-adjust` automaticamente:
// app/layout.tsx
import { Inter } from "next/font/google";
const inter = Inter({
subsets: ["latin"],
display: "swap",
variable: "--font-inter",
adjustFontFallback: true, // chave para CLS = 0
});
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="pt-PT" className={inter.variable}>
<body>{children}</body>
</html>
);
}
Para conteúdo dinâmico — carrosséis, recomendações personalizadas, badges de stock — a regra de ouro é reservar sempre espaço com `min-height` ou `aspect-ratio` em CSS. Nunca usar `display: none` seguido de injeção tardia; preferir um skeleton com as dimensões finais já definidas. Para banners de consentimento RGPD (obrigatórios pela CNPD), colocá-los em `position: fixed` no fundo do viewport em vez de empurrarem o conteúdo é a abordagem que respeita simultaneamente o utilizador e o CLS.
Particularmente em PLPs (páginas de listagem) com lazy loading infinito, garantir que cada cartão de produto tem altura fixa antes da imagem carregar. Tipicamente isto faz-se com:
.product-card {
aspect-ratio: 3 / 4;
contain: layout style paint;
content-visibility: auto;
contain-intrinsic-size: 0 480px;
}
A combinação `content-visibility: auto` + `contain-intrinsic-size` permite ao browser saltar completamente o rendering de cartões fora do viewport, melhorando simultaneamente o LCP e o INP. Cuidado: `content-visibility` ainda não funciona perfeitamente no Safari iOS 16, mas com fallback gracioso é seguro usar em 2026.
4. Edge caching em Portugal: o factor Lisboa-node
Mesmo com tudo o resto perfeito, se o seu HTML é gerado em Frankfurt ou Paris, o utilizador no Algarve está a pagar 25-40 ms só de RTT. Os principais CDNs globais têm hoje POPs em Lisboa: Cloudflare desde 2019, Fastly desde 2022, Vercel Edge Network desde 2023 (parceiro AWS no datacenter de Sines), e AWS CloudFront com edge em Lisboa desde inícios de 2024. Aproveitar este edge é trivial mas frequentemente esquecido.
Com Next.js 15 a abordagem mais eficaz combina o ISR (Incremental Static Regeneration) com cache tags para invalidação cirúrgica:
// app/produto/[slug]/page.tsx
import { unstable_cache } from "next/cache";
const getProduct = unstable_cache(
async (slug: string) => {
const res = await fetch(`${process.env.API_URL}/products/${slug}`);
return res.json();
},
["product"],
{ revalidate: 3600, tags: ["products"] }
);
export const revalidate = 3600;
export const dynamic = "force-static";
Quando o stock muda, basta chamar `revalidateTag("products")` num webhook do PIM ou do ERP. O HTML servido a partir de Lisboa fica imediatamente actualizado, sem rebuild completo. Para componentes verdadeiramente dinâmicos — preço com IVA, stock, recomendações — usar a estratégia PPR (Partial Pre-Rendering) do Next.js 15, que serve o shell estático imediatamente do edge e hidrata as zonas dinâmicas em streaming. Resultado típico: TTFB < 100 ms para utilizadores em Portugal continental.
No `vercel.json` (ou equivalente para outros providers), forçar a região de execução para edge europeu:
{
"functions": {
"app/api/**": {
"runtime": "edge",
"regions": ["cdg1", "lhr1", "fra1"]
}
}
}
Não existe ainda região `lis1` em Vercel, mas o roteamento BGP a partir de `cdg1` (Paris) para Portugal continental tem latência média de 18 ms — suficiente para manter o TTFB confortavelmente abaixo do limiar verde. Se trabalha com volumes grandes, considere AWS Lambda@Edge directamente, que oferece edge functions executadas em Lisboa.
5. Ferramentas de monitorização: do laboratório ao campo
Existe uma diferença crítica entre dados de laboratório (lab) e dados de campo (field). O Lighthouse e o PageSpeed Insights dão-nos sintéticos úteis para detectar regressões, mas só o CrUX (Chrome User Experience Report) reflecte o que os utilizadores reais portugueses experimentam. A nossa stack de monitorização recomendada para 2026 combina três camadas:
1. CrUX API + BigQuery — para análise histórica do percentil 75 por país. A consulta segmentada por país=PRT permite ver tendências mês a mês especificamente para o mercado português:
SELECT
yyyymm,
origin,
p75_inp,
p75_lcp,
p75_cls
FROM `chrome-ux-report.country_pt.YYYYMM`
WHERE origin = 'https://www.seu-site.pt'
ORDER BY yyyymm DESC
LIMIT 12;
2. RUM próprio — o snippet `web-vitals/attribution` mostrado na secção 1, enviando para um endpoint Next.js que armazena em ClickHouse ou Postgres. Permite cortar por device, browser, rota e versão de deploy. Útil para correlacionar uma subida do INP com um deploy específico.
3. Calibre, SpeedCurve ou DebugBear — monitorização sintética com testes diários a partir de Lisboa. O Calibre oferece um plano específico para PMEs portuguesas a partir de 79€/mês que cobre 5 URLs e dá alertas Slack quando o budget de performance é ultrapassado. É a forma mais barata de detectar regressões antes de chegarem a produção.
Definir performance budgets no CI/CD é não-negociável. Um workflow GitHub Actions simples com `@unlighthouse/cli` falha o build se o LCP exceder 1,8 s ou o INP exceder 200 ms num conjunto de URLs representativos. Este é o tipo de prática que o nosso serviço de desenvolvimento web implementa por defeito em qualquer novo projeto.
6. Caso de estudo: marca portuguesa de calçado, Q1 2026
Para tornar tudo isto concreto, partilhamos os resultados de uma intervenção que realizámos no primeiro trimestre de 2026 numa marca portuguesa de calçado (volume mensal de ~45.000 sessões, ticket médio 78€). O ponto de partida era um Shopify clássico migrado para um headstart customizado em Next.js 14, com os seguintes valores de baseline medidos via CrUX em Janeiro:
- LCP (p75 mobile PT): 3,4 s — vermelho
- INP (p75 mobile PT): 412 ms — vermelho
- CLS (p75 mobile PT): 0,18 — laranja
- Taxa de conversão mobile: 0,82%
Em seis semanas aplicámos exactamente o que está descrito neste artigo: upgrade para Next.js 15 com PPR, migração para AVIF servido via Cloudflare R2 com edge em Lisboa, refactor do botão "Adicionar ao carrinho" com `useTransition` e optimistic UI, substituição da webfont auto-hospedada com `adjustFontFallback`, e fragmentação do mega-menu com `scheduler.yield()`. Resultados medidos em Abril:
- LCP (p75 mobile PT): 1,6 s — verde (-53%)
- INP (p75 mobile PT): 142 ms — verde (-66%)
- CLS (p75 mobile PT): 0,04 — verde (-78%)
- Taxa de conversão mobile: 1,31% (+60%)
- Impressões orgânicas (GSC): +34% em queries mobile não-marca
O ROI puro da intervenção, calculado sobre o aumento de receita atribuível ao crescimento de conversão, pagou-se em 38 dias. E o efeito SEO secundário — a Google premeia Core Web Vitals verdes com melhor posicionamento mobile — continuou a render frutos nos meses seguintes.
Conclusão: a janela está aberta, mas não por muito tempo
O e-commerce português está num momento raro: a maioria dos concorrentes ainda não percebeu a importância do INP e continua a culpar "a internet portuguesa" pelas más métricas. Quem agir nos próximos 6 meses ganha uma vantagem competitiva real, mensurável em milhões de euros para os players maiores e em pontos percentuais de conversão para as PMEs. O Next.js 15, o AVIF, o edge Lisboa e os padrões React modernos (Server Components, PPR, `useTransition`) tornam isto acessível a qualquer equipa de desenvolvimento competente.
Na Go To Agency trabalhamos com PMEs portuguesas e marcas internacionais com presença em Portugal para implementar exactamente este tipo de optimizações de raiz, do desenho técnico à monitorização contínua em produção. Se quer auditar os seus Core Web Vitals e perceber o potencial de ganho concreto para o seu site, peça-nos um orçamento ou conheça a nossa abordagem. A diferença entre um e-commerce que carrega em 1,5 s e um que carrega em 3,5 s é literalmente a diferença entre crescer ou estagnar em 2026.

