Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
// next/image - otimização automática, carregamento lento, srcset
import Image from "next/image";
// next/font - auto-hospedado, zero CLS, subset, peso variável
import { Inter } from "next/font/google";
const inter = Inter({
subsets: ["latin"],
display: "swap",
variable: "--font-inter",
});
export default function HeroSection() {
return (
<section className={inter.className}>
<h1 className="text-5xl font-bold">Bem-vindo</h1>
{/* Prioridade: pré-carrega para LCP, sem carregamento lento */}
<Image
src="/hero.jpg"
alt="Vitrine do produto"
width={1200}
height={600}
priority
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..."
sizes="100vw"
className="rounded-xl"
/>
</section>
);
}Quando usar isso: Sempre. Cada imagem deve usar next/image e cada fonte deve usar next/font. Estas não são otimizações para adicionar depois - são a base para qualquer aplicativo Next.js de produção.
// ---- ANTES: Imagens e fontes não otimizadas - LCP 4.2s, CLS 0.35 ----
// layout.tsx
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html>
<head>
{/* CDN de fonte externa - bloqueia a renderização, causa CLS */}
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap"
rel="stylesheet"
/>
</head>
<body style={{ fontFamily: "Inter, sans-serif" }}>{children}</body>
</html>
);
}
// page.tsx
export default function HomePage() {
return (
<div>
{/* Não otimizado: sem dimensões, sem carregamento lento, sem conversão de formato */}
<img src="/hero-original.png" alt="Herói" />
{/* PNG de 2.4MB, 3000x1500px, carregado imediatamente mesmo se abaixo da dobra */}
<h1>Nossos Produtos</h1>
<div className="grid grid-cols-3 gap-4">
{products.map((p) => (
<div key={p.id}>
{/* Sem largura/altura - causa mudança de layout */}
<img src={p.image} alt={p.name} />
<p>{p.name}</p>
</div>
))}
</div>
</div>
);
}
// ---- DEPOIS: Otimizado - LCP 1.4s, CLS 0.02 ----
// layout.tsx
import { Inter } from "next/font/google";
const inter = Inter({
subsets: ["latin"],
display: "swap",
variable: "--font-inter",
// Auto-hospedado: sem requisições externas, sem CLS da troca de fonte
// Automaticamente cria subset apenas com os caracteres usados
// Fonte variável: um arquivo cobre todos os pesos (economiza ~100KB vs arquivos separados)
});
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={inter.variable}>
<body className="font-sans">{children}</body>
</html>
);
}
// page.tsx
import Image from "next/image";
import heroImage from "@/public/hero.jpg"; // Importação estática para blur automático
export default function HomePage() {
return (
<div>
{/* Herói otimizado: pré-carregamento prioritário, placeholder de blur, srcset responsivo */}
<Image
src={heroImage}
alt="Vitrine do produto apresentando nossa última coleção"
priority // Pré-carrega para LCP - sem carregamento lento
placeholder="blur" // Mostra versão borrada instantaneamente (base64 inline)
sizes="100vw" // Imagem de largura total
quality={85} // Qualidade ligeiramente reduzida - economiza 30-40% do tamanho
className="w-full h-auto rounded-xl"
// Automático: conversão WebP/AVIF, srcset, carregamento lento (exceto prioridade)
// 2.4MB PNG -> 180KB WebP no tamanho apropriado
/>
<h1 className="text-4xl font-bold mt-8">Nossos Produtos</h1>
<div className="grid grid-cols-3 gap-4 mt-6">
{products.map((p, index) => (
<div key={p.id}>
<Image
src={p.image}
alt={p.name}
width={400}
height={300}
// Primeira linha visível ao carregar - adicione prioridade
priority={index < 3}
placeholder="blur"
blurDataURL={p.blurDataURL}
sizes="(max-width: 768px) 100vw, 33vw"
className="rounded-lg"
/>
<p className="mt-2 font-medium">{p.name}</p>
</div>
))}
</div>
</div>
);
}O que isso demonstra:
sizes responsivo: serve imagem de 400px no celular em vez da imagem de desktop de 1200pxnext/font: Inter auto-hospedado, peso variável, zero requisições externas, zero CLSnext/image - Imagens são convertidas para WebP ou AVIF sob demanda, redimensionadas para as dimensões exatas necessárias e servidas com cabeçalhos Cache-Control apropriados. A otimização ocorre no momento da requisição (ou no tempo de compilação para importações estáticas) no servidor.srcset - next/image gera um atributo srcset com múltiplos tamanhos (640, 750, 828, 1080, 1200, 1920, 2048, 3840px por padrão). O navegador seleciona a menor imagem que se encaixa na viewport, reduzindo os bytes transferidos.loading="lazy". O navegador só as busca quando entram na viewport. A prop priority desabilita o carregamento lento e adiciona um <link rel="preload"> para imagens LCP.blurDataURL. O blur aparece instantaneamente enquanto a imagem completa carrega, melhorando o desempenho percebido.next/font - Fontes são baixadas no tempo de compilação e servidas do mesmo domínio do seu aplicativo. Isso elimina a consulta DNS, conexão TCP e handshake TLS necessários para requisições de CDN de fontes externas (economiza 100-300ms).Inter como fonte variável tem ~100KB em vez de 400KB+ para arquivos separados de pesos 400, 500, 600 e 700.font-display: swap - O texto renderiza imediatamente com uma fonte de fallback, depois troca para a fonte personalizada quando carregada. Combinado com o ajuste de tamanho do next/font, isso produz CLS quase zero.Fontes personalizadas locais:
import localFont from "next/font/local";
const customFont = localFont({
src: [
{ path: "./fonts/custom-regular.woff2", weight: "400", style: "normal" },
{ path: "./fonts/custom-bold.woff2", weight: "700", style: "normal" },
],
display: "swap",
variable: "--font-custom",
});Geração de blur para imagens remotas:
// Gera blurDataURL no tempo de compilação ou requisição
import { getPlaiceholder } from "plaiceholder";
async function ProductCard({ imageUrl }: { imageUrl: string }) {
const { base64 } = await getPlaiceholder(imageUrl);
return (
<Image
src={imageUrl}
alt="Produto"
width={400}
height={300}
placeholder="blur"
blurDataURL={base64}
/>
);
}Direção de arte com imagens diferentes por breakpoint:
export function ResponsiveHero() {
return (
<picture>
<source media="(max-width: 768px)" srcSet="/hero-mobile.webp" />
<source media="(max-width: 1200px)" srcSet="/hero-tablet.webp" />
<Image
src="/hero-desktop.jpg"
alt="Herói"
width={1920}
height={800}
priority
sizes="100vw"
/>
</picture>
);
}Modo fill - a imagem preenche seu contêiner pai:
// Use quando você não sabe as dimensões exatas, ou a imagem deve
// esticar/cobrir/conter seu pai. O pai DEVE ter posição + dimensões.
export function AvatarCard({ src, name }: { src: string; name: string }) {
return (
<div className="relative h-64 w-64 overflow-hidden rounded-full">
<Image
src={src}
alt={name}
fill
sizes="256px"
className="object-cover" // cover, contain, ou fill
/>
</div>
);
}Banner de herói estilo fill + object-cover:
// Substitui background-image do CSS - obtém todas as otimizações do next/image
export function HeroBanner({ title }: { title: string }) {
return (
<section className="relative h-[60vh] w-full">
<Image
src="/hero-bg.jpg"
alt=""
fill
priority
sizes="100vw"
quality={80}
className="object-cover"
/>
{/* Overlay de conteúdo */}
<div className="relative z-10 flex h-full items-center justify-center">
<h1 className="text-5xl font-bold text-white drop-shadow-lg">{title}</h1>
</div>
</section>
);
}Grade responsiva com sizes correto:
// sizes informa ao navegador qual variante do srcset baixar ANTES do layout.
// Sem ele, o navegador baixa a variante de 3840px.
export function ProductGrid({ products }: { products: Product[] }) {
return (
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
{products.map((p, i) => (
<div key={p.id} className="relative aspect-square">
<Image
src={p.image}
alt={p.name}
fill
// Corresponde à grade: 1 coluna no mobile, 2 no sm, 4 no lg
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 25vw"
priority={i < 4} // pré-carrega apenas a primeira linha
className="rounded-lg object-cover"
/>
</div>
))}
</div>
);
}Carregador de imagem personalizado (Cloudinary, Imgix, CDN personalizado):
import Image from "next/image";
// Carregador personalizado: next/image chama isso para gerar a URL para cada entrada srcset
function cloudinaryLoader({ src, width, quality }: { src: string; width: number; quality?: number }) {
return `https://res.cloudinary.com/demo/image/upload/w_${width},q_${quality || 75}/${src}`;
}
export function CloudinaryImage({ publicId, alt }: { publicId: string; alt: string }) {
return (
<Image
loader={cloudinaryLoader}
src={publicId}
alt={alt}
width={800}
height={600}
sizes="(max-width: 768px) 100vw, 50vw"
/>
);
}
// Ou defina globalmente em next.config.ts:
// images: { loader: "custom", loaderFile: "./lib/image-loader.ts" }Imagens SVG e ícones (ignora otimização):
// next/image otimiza imagens raster (JPEG, PNG, WebP).
// Para SVGs, ignore a otimização - eles já são vetoriais e pequenos.
export function Logo() {
return (
<Image
src="/logo.svg"
alt="Acme Inc"
width={120}
height={40}
unoptimized // SVGs não precisam de redimensionamento ou conversão de formato
/>
);
}
// Para SVGs inline com controle de cor, importe como um componente React em vez disso:
// import Logo from "./logo.svg"; // requer @svgr/webpackGaleria abaixo da dobra com carregamento lento e loading sobrescrito:
// Padrão: imagens carregam lentamente. Mas você pode ser explícito.
// Útil para documentação ou ao combinar com Intersection Observer.
export function Gallery({ images }: { images: string[] }) {
return (
<div className="columns-2 gap-4 lg:columns-3">
{images.map((src, i) => (
<Image
key={src}
src={src}
alt={`Imagem da galeria ${i + 1}`}
width={600}
height={400}
loading="lazy" // explícito - o mesmo que o padrão, mas com intenção clara
placeholder="blur"
blurDataURL="data:image/svg+xml;base64,..." // pequeno shimmer SVG
sizes="(max-width: 1024px) 50vw, 33vw"
className="mb-4 rounded-lg"
/>
))}
</div>
);
}Importações estáticas com blur automático (nenhum blurDataURL necessário):
// Quando você importa um arquivo de imagem local, o Next.js fornece largura, altura
// e blurDataURL automaticamente no tempo de compilação. Nenhum valor manual é necessário.
import productShot from "@/public/images/product-shot.jpg";
export function ProductHero() {
return (
<Image
src={productShot} // StaticImageData - inclui largura, altura, blur
alt="Foto do produto"
placeholder="blur" // blur funciona automaticamente para importações estáticas
priority
sizes="100vw"
className="w-full"
// Nenhuma largura, altura ou blurDataURL necessária - tudo inferido da importação
/>
);
}Configuração remotePatterns com protocolo e pathname:
// next.config.ts - controle granular sobre fontes de imagem externas permitidas
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "cdn.example.com",
pathname: "/images/**", // permite apenas o caminho /images/
},
{
protocol: "https",
hostname: "*.unsplash.com", // subdomínio wildcard
},
{
protocol: "https",
hostname: "avatars.githubusercontent.com",
},
],
// Sobrescreve tamanhos de dispositivo padrão para geração de srcset
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
// Sobrescreve tamanhos de imagem para a prop `sizes` (variantes menores)
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
// Formatos preferidos - Next.js tenta AVIF primeiro, depois WebP
formats: ["image/avif", "image/webp"],
},
};
export default nextConfig;Placeholder Shimmer / Esqueleto (SVG personalizado):
// Em vez de uma imagem borrada, mostre um efeito shimmer animado
const shimmer = (w: number, h: number) => `
<svg width="${w}" height="${h}" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="g">
<stop stop-color="#f6f7f8" offset="0%" />
<stop stop-color="#edeef1" offset="50%" />
<stop stop-color="#f6f7f8" offset="100%" />
</linearGradient>
</defs>
<rect width="${w}" height="${h}" fill="url(#g)" />
</svg>`;
function toBase64(str: string) {
return typeof window === "undefined"
? Buffer.from(str).toString("base64")
: window.btoa(str);
}
export function ShimmerImage({ src, alt }: { src: string; alt: string }) {
return (
<Image
src={src}
alt={alt}
width={400}
height={300}
placeholder="blur"
blurDataURL={`data:image/svg+xml;base64,${toBase64(shimmer(400, 300))}`}
/>
);
}priority - Normalmente uma por página. Sem isso, sua imagem de herói carrega lentamente e o LCP sofre em 500ms+.sizes - Sem sizes, o navegador baixa a variante de 3840px para uma miniatura de 300px. Combine sizes com os breakpoints de CSS/layout.alt - Imagens decorativas recebem alt="" (string vazia, não omitida). Imagens significativas recebem texto alt descritivo.fill quando as dimensões são desconhecidas - Avatares enviados por usuários, imagens de CMS com proporções variáveis. O pai deve ter position: relative e dimensões explícitas.width, height e blurDataURL automáticos no tempo de compilação. Nenhum valor manual para manter.remotePatterns, não domains - domains está obsoleto. remotePatterns suporta curingas e restrições de caminho para segurança.formats: ["image/avif", "image/webp"] - AVIF é 50% menor que JPEG. Next.js serve AVIF para navegadores compatíveis e volta para WebP.unoptimized em imagens raster - Use apenas para SVGs. Imagens raster (JPEG, PNG) devem sempre passar pelo pipeline de otimização.quality={75-85} para fotos - O padrão é 75. Aumente para 80-85 para imagens de herói onde a qualidade é importante. Abaixo de 70, artefatos JPEG se tornam visíveis.priority. Tudo abaixo da dobra permanece lento (o padrão).Comparação de formatos de imagem:
| Formato | Compressão | Suporte do Navegador | Melhor Para |
|---|---|---|---|
| JPEG | Bom | Universal | Fotos, imagens complexas |
| WebP | 25-35% menor que JPEG | 97%+ navegadores | Escolha padrão para a maioria das imagens |
| AVIF | 50% menor que JPEG | 92%+ navegadores | Compressão máxima quando suportado |
| PNG | Sem perdas | Universal | Ícones, capturas de tela com texto |
| SVG | Vetor | Universal | Logos, ícones, ilustrações |
next/image fornece verificação de tipo completa para props, incluindo src, width, height, alt.import img from "./photo.jpg") são tipadas como StaticImageData com width, height e blurDataURL automáticos.next/font/google e next/font/local retornam objetos com propriedades className, variable e style.Falta de priority na imagem LCP - A maior imagem visível (herói, foto do produto) carrega lentamente por padrão, atrasando o LCP. Correção: Adicione priority ao elemento que é a Maior Imagem de Conteúdo (LCP). Geralmente uma por página.
Atributo sizes incorreto - Sem sizes, o navegador assume que a imagem tem 100vw e baixa a maior variante srcset. Uma imagem de cartão de 400px baixa em 3840px. Correção: Defina sizes para corresponder à largura real renderizada: sizes="(max-width: 768px) 100vw, 33vw".
Imagens externas sem configuração - next/image rejeita URLs externas a menos que configurado. Correção: Adicione domínios em next.config.ts: images: { remotePatterns: [{ hostname: "cdn.example.com" }] }.
Falta de largura e altura - Imagens sem dimensões causam mudança de layout. O navegador não pode reservar espaço até que a imagem carregue. Correção: Sempre forneça width e height, ou use fill com um contêiner pai posicionado.
Flash de carregamento de fonte - Usar @import ou <link> para fontes do Google Fonts causa um flash de texto não estilizado e CLS. Correção: Use next/font/google exclusivamente. Nunca adicione tags <link> para Google Fonts.
Muitos pesos de fonte - Carregar 6+ pesos de fonte aumenta significativamente o tamanho total do arquivo da fonte. Correção: Use uma fonte variável e limite aos pesos realmente usados no seu sistema de design (tipicamente 400, 500, 700).
Usando fill sem um pai posicionado - A imagem renderiza com position: absolute e transborda seu contêiner, cobrindo outro conteúdo. Correção: O pai deve ter position: relative (ou absolute/fixed) e dimensões explícitas ou proporção.
Usando domains em vez de remotePatterns - domains está obsoleto e não suporta restrições de caminho ou curingas. Correção: Mude para remotePatterns com protocol, hostname e pathname para segurança.
Omitindo sizes em imagens de grade/cartão - Uma imagem de cartão de 25vw baixa a variante de 3840px (10x maior que o necessário). Correção: Sempre defina sizes para corresponder ao seu layout: "(max-width: 768px) 100vw, 25vw".
Usando background-image do CSS em vez de next/image - Perde otimização automática, carregamento lento, srcset e conversão de formato. Correção: Use fill + object-cover como mostrado na variação do banner de herói acima.
| Abordagem | Contraponto |
|---|---|
next/image | Otimização automática; requer Next.js |
| Cloudinary ou Imgix | Otimização baseada em CDN; dependência externa e custo |
<img> com srcset manual | Controle total; sem otimização automática |
background-image CSS | Não pode usar next/image; perde carregamento lento e srcset |
next/font | Zero CLS, auto-hospedado; apenas Next.js |
| Fontsource | Pacotes npm auto-hospedados; configuração manual |
| Fontes variáveis via CDN | Arquivo único; ainda tem sobrecarga de requisição externa |
<link rel="preload"> no <head> do HTML para a imagem.srcset garante que o navegador baixe apenas o tamanho necessário para a viewport.quality (padrão 75) pode ser ajustada para economias adicionais.font-display: swap combinado com o ajuste automático de tamanho garante CLS quase zero.blurDataURL (por exemplo, através da biblioteca plaiceholder).sizes, o navegador assume que a imagem tem largura 100vw.srcset (até 3840px) mesmo para uma imagem de cartão de 400px.sizes para corresponder à largura real renderizada, por exemplo, sizes="(max-width: 768px) 100vw, 33vw".// next.config.ts
const nextConfig = {
images: {
remotePatterns: [
{ hostname: "cdn.example.com" },
{ hostname: "images.unsplash.com" },
],
},
};<link> externas para fontes bloqueiam a renderização e causam um flash de texto não estilizado.next/font/google exclusivamente -- ele auto-hospeda a fonte no tempo de compilação.import { Inter } from "next/font/google";
// Retorna { className: string; variable: string; style: { fontFamily: string } }
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
// Use className em elementos ou variable em <html> para acesso a variável CSS
<html className={inter.variable}>fill quando a imagem deve esticar para preencher seu contêiner pai.position: relative e dimensões definidas.import localFont from "next/font/local";
const customFont = localFont({
src: [
{ path: "./fonts/custom-regular.woff2", weight: "400" },
{ path: "./fonts/custom-bold.woff2", weight: "700" },
],
display: "swap",
variable: "--font-custom",
});Revisado por Chris St. John·Última atualização: 10 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥