Criando um pipeline de otimização de imagens com uma CDN
Um pipeline de imagens combina armazenamento, transformações, marcação responsiva e cache. Uma CDN pode reduzir o trabalho de entrega, mas URLs de transformação e regras de assinatura variam de provedor para provedor. Este guia usa a interface de imagens hospedadas do Cloudflare Images e deixa esses contratos explícitos.
Entendendo CDNs de imagem
Serviços de imagem podem criar variantes em tamanhos diferentes, negociar formatos compatíveis e armazenar os resultados em cache. Verifique quais operações acontecem no momento do upload ou no momento da entrega, quais formatos são compatíveis e como chaves de cache e controles de acesso interagem. Nem toda CDN processa imagens sem um serviço de transformação configurado separadamente.
Escolhendo um provedor de otimização de imagens
Compare formatos compatíveis, comportamento de redimensionamento, entrega privada, limites operacionais e integração com o seu armazenamento. Os exemplos a seguir usam as variantes predefinidas do Cloudflare Images, e não a API separada de transformação de imagens remotas dele. Use os contratos de URL e de assinatura documentados pelo provedor escolhido, em vez de tratar parâmetros de consulta de CDNs de imagem como intercambiáveis.
Usando o Azure Front Door para entrega de imagens
O Azure Front Door pode entregar conteúdo a partir de uma origem de processamento de imagens, mas colocar imagens atrás dele não cria, por si só, variantes redimensionadas. Mantenha separados o serviço de transformação, a política de acesso à origem e a configuração de cache. Consulte a documentação do Azure Front Door para ver a configuração de origem e de entrega desse provedor.
Configurando a otimização de imagens
Faça upload de uma imagem para o Cloudflare Images e obtenha o hash da conta e o ID da imagem. No
painel, crie variantes predefinidas chamadas w320, w640 e w1280 com as larguras correspondentes, proporção
preservada e ajuste scale-down. Consulte a
documentação de configuração de variantes.
Para este exemplo, use uma imagem de origem com pelo menos 1.280 pixels de largura, para que cada
descritor de largura em srcset corresponda a uma largura de saída real.
Salve este construtor de URLs compartilhado como images.ts. A configuração de variantes é definida uma
única vez e reutilizada na entrega responsiva e na assinatura. Este exemplo aceita deliberadamente IDs
de imagem opacos, sem segmentos de caminho personalizados.
export const imageVariants = [
{ name: 'w320', width: 320 },
{ name: 'w640', width: 640 },
{ name: 'w1280', width: 1280 },
]
export function getOptimizedImageUrl(accountHash: string, imageId: string, variant: string): string {
const segment = /^[A-Za-z0-9_-]{1,128}$/
if (!segment.test(accountHash) || !segment.test(imageId)) {
throw new Error('Use an account hash and opaque image ID, not a URL or path')
}
if (!imageVariants.some(({ name }) => name === variant)) {
throw new Error('Unknown image variant')
}
return new URL(`https://imagedelivery.net/${accountHash}/${imageId}/${variant}`).href
}
URLs de entrega de imagens hospedadas trazem o hash da conta, o ID da imagem e a variante no
caminho. Anexar parâmetros de consulta arbitrários width ou format a uma URL incompleta não é uma
API equivalente. O Cloudflare pode negociar formatos de entrega com base na requisição do navegador;
tenha em mente o
contrato completo de entrega de imagens hospedadas
ao adicionar outro proxy ou cache.
Implementando imagens responsivas
Mantenha o componente React independente de as URLs serem públicas ou assinadas. Passe para ele as
fontes geradas, as dimensões intrínsecas e um valor de sizes que corresponda ao layout real:
import type { ReactNode } from 'react'
interface ResponsiveImageProps {
sources: { url: string; width: number }[]
alt: string
width: number
height: number
sizes: string
loading?: 'lazy' | 'eager'
}
export function ResponsiveImage({
sources, alt, width, height, sizes, loading = 'lazy',
}: ResponsiveImageProps): ReactNode {
const fallback = sources[sources.length - 1]
if (!fallback || !Number.isFinite(width) || width <= 0 || !Number.isFinite(height) || height <= 0) {
throw new Error('Supply image sources and positive intrinsic dimensions')
}
return (
<img
src={fallback.url}
srcSet={sources.map((source) => `${source.url} ${source.width}w`).join(', ')}
width={width}
height={height}
alt={alt}
sizes={sizes}
loading={loading}
className="responsive-image"
/>
)
}
Aplique uma regra CSS responsiva na folha de estilos da sua aplicação:
.responsive-image {
display: block;
max-width: 100%;
height: auto;
}
Para imagens públicas, monte a prop sources a partir da lista compartilhada de variantes:
import { getOptimizedImageUrl, imageVariants } from './images.ts'
export function publicImageSources(accountHash: string, imageId: string) {
return imageVariants.map(({ name, width }) => ({
width,
url: getOptimizedImageUrl(accountHash, imageId, name),
}))
}
As props width e height descrevem a proporção da imagem de origem e reservam espaço no layout. Use
carregamento imediato (eager) para a imagem que provavelmente será o LCP, em vez de carregar todas as
imagens sob demanda (lazy). Mantenha um texto alternativo significativo mesmo quando a entrega
falhar; não tente carregar indefinidamente uma URL de fallback inexistente.
Monitorando o desempenho
Use a versão atual do pacote web-vitals e registre LCP, INP e CLS. O INP substitui a métrica FID, que foi
retirada de uso. Chame este inicializador, exclusivo do navegador, uma única vez, depois de cumprir a
política de consentimento de analytics da sua aplicação. Implemente o endpoint /analytics de mesma origem
antes de ativar o envio.
import { onCLS, onINP, onLCP, type Metric } from 'web-vitals'
function sendToAnalytics({ name, value, id }: Pick<Metric, 'name' | 'value' | 'id'>): void {
const body = JSON.stringify({ name, value, id })
if (typeof navigator.sendBeacon === 'function' && navigator.sendBeacon('/analytics', body)) return
void fetch('/analytics', { body, method: 'POST', keepalive: true })
.then((response) => {
if (!response.ok) throw new Error('Analytics request failed')
})
.catch(() => console.warn('Could not deliver performance metric'))
}
export function initializePerformanceMonitoring(): void {
onLCP(sendToAnalytics)
onINP(sendToAnalytics)
onCLS(sendToAnalytics)
}
Um beacon enfileirado não comprova o recebimento no servidor. Compare as métricas de campo com medições controladas em navegador e com as taxas de falha nas requisições de imagens. Não envie URLs de imagem assinadas completas nem conteúdo de usuários para o analytics como identificadores de métricas.
Considerações de segurança
A assinatura acontece somente no servidor, depois de autenticar quem faz a chamada e autorizar o acesso à imagem. Armazene a chave de assinatura do Images como um segredo do servidor; ela não é o hash da conta nem um token de API, e nunca deve entrar nos bundles do navegador. Imagens privadas devem exigir URLs assinadas, e as variantes delas não devem ser configuradas para contornar essa exigência.
Salve este helper, exclusivo para Node.js, como images.server.ts. Ele reutiliza o mesmo construtor de caminhos,
adiciona o parâmetro exp do provedor e assina o caminho e a query completos antes de adicionar sig:
import { createHmac } from 'node:crypto'
import { getOptimizedImageUrl } from './images.ts'
export function getSecureImageUrl(
accountHash: string,
imageId: string,
variant: string,
signingKey: string,
expiresIn = 3600,
): string {
if (!signingKey || !Number.isInteger(expiresIn) || expiresIn < 1 || expiresIn > 86400) {
throw new Error('A signing key and bounded expiry are required')
}
const url = new URL(getOptimizedImageUrl(accountHash, imageId, variant))
url.searchParams.set('exp', String(Math.floor(Date.now() / 1000) + expiresIn))
const payload = `${url.pathname}?${url.searchParams.toString()}`
url.searchParams.set('sig', createHmac('sha256', signingKey).update(payload).digest('hex'))
return url.href
}
Para imagens privadas, gere no servidor todas as URLs de origem responsivas e passe ao componente apenas essas URLs assinadas. Alterar a variante ou a expiração altera o payload assinado. Siga a documentação de assinatura de imagens privadas e não exponha um assinador público que aceite IDs de imagem arbitrários sem verificar a propriedade. Até expirar, uma assinatura funciona como um token ao portador, e não substitui a autorização.
Configure uma Content Security Policy no nível da página que permita a origem real de entrega, como
https://imagedelivery.net. Um curinga para os subdomínios dela não inclui o próprio hostname. Mescle essa
diretiva à política completa da aplicação, em vez de substituir diretivas não relacionadas. Ative
apenas políticas HSTS que todo o escopo de domínio afetado consiga suportar via HTTPS.
Boas práticas
- Derive as URLs responsivas de uma única configuração de variantes verificada.
- Faça
sizescorresponder ao layout e os descritores de largura corresponderem às larguras de saída reais. - Preserve a proporção intrínseca para reduzir mudanças de layout.
- Mantenha as chaves de assinatura no servidor e autorize o acesso antes de emitir URLs privadas.
- Teste o comportamento do cache e a expiração; adicionar uma assinatura não torna privada uma imagem configurada como pública.
- Compare tamanhos reais de arquivo, qualidade visual e métricas de página, em vez de prometer um ganho de velocidade universal.
Conclusão
Um pipeline de imagens confiável usa os contratos reais de URL e de assinatura do provedor, marcação responsiva e um comportamento de entrega observável. Mantenha centralizada a configuração compartilhada de variantes e teste separadamente os fluxos públicos e privados. Para processamento e entrega gerenciados, conheça o Smart CDN da Transloadit.
