Pular para o conteúdo

AWS · parte 8 de 11

CloudFront: entrega de conteúdo pela borda

Como o CloudFront entrega conteúdo pela borda com cache, o que acontece em um cache hit e em um miss e como servir sites estáticos.

Revisada em · 12 min de leitura

Tamanho da fonte:
Largura da coluna:

O Amazon CloudFront é o serviço de CDN (Content Delivery Network, ou rede de distribuição de conteúdo) da AWS. Ele acelera a entrega de sites estáticos, vídeos, APIs e outros recursos para usuários espalhados pelo mundo. Como os arquivos ficam guardados perto de quem pede, a latência cai, e o servidor de origem recebe bem menos requisições.

O restante desta nota explica como o serviço funciona e como usá-lo de verdade, do primeiro bucket privado até a invalidação de cache depois de um deploy.

1. Como funciona a rede de distribuição global

Sem CDN, todos os usuários do mundo fariam requisições diretamente para o servidor ou o bucket principal, que fica em uma única região, como Virgínia ou São Paulo. O CloudFront coloca uma rede mundial de Pontos de Presença (edge locations, ou PoPs) entre o usuário e a origem. Cada PoP guarda cópias dos arquivos que já foram pedidos naquela região.

aws cloudfront cdn
figura 1 · aws cloudfront cdn

Uma requisição segue um de dois caminhos. No cache hit, o PoP mais próximo já tem a cópia e responde sozinho, sem consumir banda da origem. No cache miss, o PoP não tem o arquivo, busca na origem, entrega ao usuário e guarda a cópia para as próximas requisições. O diagrama abaixo mostra os dois casos lado a lado: a primeira visitante causa o miss, e a segunda recebe o hit.

cloudfront 01 cache miss hit
figura 2 · cloudfront 01 cache miss hit

Quem paga o custo do miss é o primeiro visitante, que espera a busca na origem. A partir daí, o custo recai sobre quem pede a cópia depois. Por isso um cache com TTL longo é ótimo para arquivos que não mudam e ruim para uma página que muda a cada deploy. A seção sobre invalidação explica como lidar com isso.

2. Origens suportadas

O CloudFront busca conteúdo em três tipos de origem.

Amazon S3 é a origem mais comum para sites estáticos, vídeos, PDFs e mídia. Com OAC (Origin Access Control), o bucket pode ficar totalmente privado, e todo acesso passa pelo CloudFront. A seção prática detalha essa configuração.

Application Load Balancer (ligado a instâncias EC2) ou outras APIs servem para tráfego dinâmico. Mesmo quando não há cache, a distribuição mantém conexões TCP otimizadas com a origem pelo backbone da AWS, o que ajuda rotas como /api/*.

Servidores externos, fora da AWS, também podem ser origem, desde que respondam por HTTP ou HTTPS.

3. Vantagens estratégicas

A primeira vantagem é a latência. O usuário negocia o handshake TLS com o PoP mais próximo, e não com a origem distante. O número exato de milissegundos depende da rota, e não foi medido nesta revisão.

A segunda é o custo de transferência. Dados que saem de um bucket S3 para a internet costumam ser mais caros do que os que saem pelo CloudFront, e a transferência do S3 para o CloudFront é gratuita. Confira a tabela de preços atual antes de fazer contas, porque esses valores não foram consultados aqui.

A terceira é a segurança na borda. O CloudFront vem com o AWS Shield Standard, que mitiga ataques de negação de serviço comuns na camada de rede, e pode ser combinado com o AWS WAF para filtrar SQL Injection, XSS e IPs maliciosos antes de chegarem à origem.

A quarta é o certificado HTTPS. Um domínio próprio, como cdn.seusite.com, recebe um certificado do AWS Certificate Manager (ACM) sem custo, renovado automaticamente. Há uma restrição de região que você precisa conhecer, explicada na seção prática.

4. Quando usar o CloudFront

SPAs (Single Page Applications) em React, Vue, Angular ou Next.js estático ficam no S3 e são entregues globalmente pelo CloudFront. Mídia e streaming, como fotos, PDFs e áudio ou vídeo sob demanda, também se beneficiam do cache. Aceleração de APIs faz sentido quando os usuários estão em vários países e o endpoint pode ser cacheado ou, pelo menos, roteado pela rede da AWS.

Usando o CloudFront na prática

Esta parte segue o fluxo do Grijp.Blog: o site é gerado como arquivos estáticos, enviado para um bucket S3 na região sa-east-1 e entregue pelo CloudFront. Os IDs de distribuição, de conta e de bucket são placeholders. A configuração real do blog não foi revisada nesta nota.

Criar a distribuição para um bucket privado

A forma recomendada hoje é manter o bucket privado e deixar só o CloudFront ler dele, usando OAC (Origin Access Control). O OAC substitui a antiga OAI (Origin Access Identity), e a própria documentação recomenda o OAC para novas distribuições, porque ele suporta todas as regiões, SSE-KMS e requisições PUT e DELETE dinâmicas.

Os passos são estes. Primeiro, crie o bucket com o acesso público bloqueado, conforme a nota sobre o S3. Depois, crie o OAC. Pela CLI, isso fica assim:

aws cloudfront create-origin-access-control \
  --origin-access-control-config Name=oac-blog,Description="OAC do bucket do blog",SigningProtocol=sigv4,SigningBehavior=always,OriginAccessControlOriginType=s3

O SigningBehavior=always faz o CloudFront assinar todas as requisições que manda ao bucket, que é o valor recomendado. O campo Id da resposta é o que você liga à distribuição. Em seguida, crie a distribuição (no console, é mais simples; na CLI, o JSON da distribuição é grande), escolha o bucket como origem e associe o OAC a essa origem.

Por fim, a bucket policy precisa autorizar o serviço do CloudFront, limitado à sua distribuição:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontServicePrincipalReadOnly",
      "Effect": "Allow",
      "Principal": { "Service": "cloudfront.amazonaws.com" },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::meu-blog-exemplo/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::111122223333:distribution/DIST_ID"
        }
      }
    }
  ]
}

Os valores 111122223333 (conta) e DIST_ID são placeholders. O Condition é o que impede outra distribuição, mesmo de outra conta, de ler o bucket. O diagrama mostra o caminho permitido e o bloqueado:

cloudfront 02 oac bucket privado
figura 3 · cloudfront 02 oac bucket privado

Três detalhes evitam dor de cabeça. O bucket precisa ser um bucket comum, e não um bucket configurado como website endpoint de S3 (hospedagem estática do próprio S3), porque OAC não funciona com esse tipo de origem. Os objetos cifrados com SSE-KMS exigem que a chave KMS também tenha uma declaração para o CloudFront. E a distribuição leva algum tempo para propagar a nova configuração pelos PoPs, então um 403 logo depois da mudança pode ser só propagação.

Certificado e domínio próprio

Para usar um domínio como blog.exemplo.com, o certificado precisa ser emitido no ACM na região us-east-1 (Norte da Virgínia), mesmo que o bucket esteja em sa-east-1. Essa é uma exigência do CloudFront, e não uma escolha do serviço. O certificado também precisa cobrir o nome alternativo que você adiciona à distribuição, seja pelo nome exato ou por um wildcard do mesmo nível. Se não cobrir, o CloudFront recusa a associação.

Página de erro para SPA

Uma SPA resolve as rotas no navegador. Quando o usuário abre /pedidos/42 diretamente, o CloudFront pede ao bucket um objeto chamado pedidos/42, que não existe. Com o bucket privado, a S3 responde 403 (o comportamento esperado aqui é esse, mas não foi reproduzido nesta revisão). O erro precisa virar index.html com status 200, para que o roteador do próprio JavaScript monte a página:

cloudfront 05 spa pagina de erro
figura 4 · cloudfront 05 spa pagina de erro

No console, abra a distribuição, vá em Error pages e crie uma resposta personalizada para o código 403 e outra para o 404, apontando para /index.html com código de resposta 200. A documentação descreve cada passo na seção de respostas de erro personalizadas.

Essa troca tem um custo. Um asset que realmente não existe, como uma imagem quebrada, também vira index.html com 200, e o erro some do lado do cliente. Por isso, muitas equipes limitam a regra ao prefixo das rotas da SPA, quando a configuração permite fazer isso sem uma função de borda. A documentação também permite controlar por quanto tempo os erros ficam em cache, o que é útil para não manter um erro transitório por muito tempo.

Invalidação de cache

Depois de um deploy, a borda ainda pode ter a cópia antiga de um arquivo até o TTL dela expirar. Há duas saídas. A primeira é invalidar: você pede ao CloudFront que remova as cópias de certos caminhos, e a próxima requisição vai à origem. A segunda é versionar o nome do arquivo, como app.3f9a1c.js, que cria um arquivo novo sem depender de invalidação. A própria documentação recomenda a segunda opção quando os arquivos mudam com frequência, porque a invalidação pode deixar versões antigas em caches de terceiros, como proxies corporativos, até elas expirarem. A figura compara os dois casos:

cloudfront 04 invalidacao copia velha
figura 5 · cloudfront 04 invalidacao copia velha

A invalidação pela CLI é esta:

aws cloudfront create-invalidation \
  --distribution-id DIST_ID \
  --paths "/*"

Os caminhos são separados por espaço. Com --paths, a CLI gera um CallerReference aleatório. Para controlar esse valor, use um arquivo com --invalidation-batch:

{
  "Paths": {
    "Quantity": 2,
    "Items": ["/index.html", "/feed.xml"]
  },
  "CallerReference": "deploy-2026-10-27-1"
}
aws cloudfront create-invalidation --distribution-id DIST_ID --invalidation-batch file://inv-batch.json

Dois números do pricing importam. Os primeiros 1.000 caminhos de invalidação por mês são gratuitos, e cada caminho acima disso é cobrado. Um caminho com * conta como um só, mesmo que atinja milhares de arquivos, então /* custa o mesmo que /index.html. Isso faz a invalidação de /* ser barata, mas não a torna gratuita em um deploy diário com muitos caminhos específicos.

Para usar a API em código, este é o trecho em Go. Compilei e checkei o tipo localmente, mas não o executei contra a AWS:

func invalidar(ctx context.Context, cfg aws.Config, distID string, caminhos []string) (*string, error) {
	cf := cloudfront.NewFromConfig(cfg)
	out, err := cf.CreateInvalidation(ctx, &cloudfront.CreateInvalidationInput{
		DistributionId: aws.String(distID),
		InvalidationBatch: &types.InvalidationBatch{
			CallerReference: aws.String(fmt.Sprint(time.Now().UnixNano())),
			Paths: &types.Paths{
				Quantity: aws.Int32(int32(len(caminhos))),
				Items:    caminhos,
			},
		},
	})
	if err != nil {
		return nil, err
	}
	return out.Invalidation.Id, nil
}

E o equivalente em TypeScript, com @aws-sdk/client-cloudfront, também compilado e checado localmente sem execução contra a AWS:

import { CloudFrontClient, CreateInvalidationCommand } from "@aws-sdk/client-cloudfront";

const cf = new CloudFrontClient({});

export async function invalidar(distId: string, caminhos: string[]): Promise<string | undefined> {
  const r = await cf.send(
    new CreateInvalidationCommand({
      DistributionId: distId,
      InvalidationBatch: {
        CallerReference: String(Date.now()),
        Paths: { Quantity: caminhos.length, Items: caminhos },
      },
    }),
  );
  return r.Invalidation?.Id;
}

O CallerReference precisa ser único para cada pedido. Timestamp serve, e a documentação sugere um valor desse tipo.

Deploy do blog de ponta a ponta

O fluxo completo tem três passos, nesta ordem. Primeiro o build gera dist/. Depois a sincronização envia os arquivos ao bucket, com os cabeçalhos de cache de acordo com o tipo de arquivo (veja a nota sobre o S3). Por fim a invalidação remove as cópias antigas da borda. A invalidação vem depois do upload: se ela rodasse antes, uma requisição que chegasse no meio do deploy poderia guardar a versão antiga de novo.

pnpm build
aws s3 sync dist/ s3://meu-blog-exemplo --delete
aws cloudfront create-invalidation --distribution-id DIST_ID --paths "/*"

O diagrama mostra cada etapa na coluna onde ela acontece:

cloudfront 03 deploy blog
figura 6 · cloudfront 03 deploy blog

Se você usar && entre os comandos, a invalidação só roda quando o sync termina sem erro, o que é o comportamento desejado. Com o --delete do sync, lembre-se do que foi dito na nota do S3: ele apaga do bucket tudo que não está em dist/.

Verificar se o cache está funcionando

Uma forma prática de conferir é pedir o cabeçalho de uma URL com curl -I, repetindo a chamada em seguida, e procurar o cabeçalho x-cache. Ele costuma indicar se a resposta veio do cache da borda ou da origem. Esse nome e o formato do valor não foram conferidos nesta revisão, então confirme na sua distribuição antes de depender deles.

curl -I https://blog.exemplo.com/logo.png

Erros comuns

O site continua mostrando a versão antiga depois do deploy. Quase sempre é TTL. Invalide os caminhos alterados, ou, melhor ainda, use nomes com hash nos assets e deixe só o HTML com cache curto.

Depois de configurar o OAC, o CloudFront devolve AccessDenied. Confira se a bucket policy tem o SourceArn exato da distribuição e se a distribuição já terminou de propagar. Um erro de ARN, como um ID errado, é o caso mais comum.

O navegador recusa o certificado do domínio próprio. Verifique se o certificado está em us-east-1 e se cobre o nome exato adicionado como alternate domain name.

A rota da SPA dá 404 ou 403 ao ser acessada direto. Falta a página de erro personalizada apontando para /index.html com código 200.

A política gerenciada CachingOptimized tem TTL padrão de 24 horas e máximo de 365 dias, e seu TTL mínimo é de 1 segundo. Pela documentação, quando o TTL mínimo é maior que zero, o CloudFront cacheia pelo menos esse tempo mesmo que a origem mande Cache-Control: no-cache. Para que os cabeçalhos da origem mandem no cache, a política certa é a UseOriginCacheControlHeaders.

Por fim, invalidar com muita frequência pode gerar custo. Acima de 1.000 caminhos por mês, cada caminho é cobrado. Versionar os assets reduz esse volume.

Fontes

Consultadas em 27/10/2026. Os limites de invalidação, a configuração de OAC, as regras de certificado no ACM e as políticas de cache gerenciadas vêm da documentação oficial listada abaixo. Os exemplos de código em Go e TypeScript foram compilados e checados localmente, sem chamada à AWS.

Não verificado nesta revisão

Os números de Pontos de Presença, a latência típica de um cache hit e a gratuidade da transferência do S3 para o CloudFront não foram conferidos nesta revisão. A nota descreve o comportamento sem citar esses números.

Preços por requisição e por transferência não foram consultados, porque a página de preço é dinâmica.

O nome e o formato do cabeçalho x-cache não foram conferidos contra uma distribuição real.

O comportamento de 403 do S3 para chave inexistente com OAC não foi reproduzido.

A invalidação não foi executada contra uma distribuição real, e o tempo que ela leva para concluir não foi medido.

↑ Voltar ao topo

Atalhos de teclado

Abrir busca
/
Alternar tema claro/escuro
t
Aula anterior na série
←
Próxima aula na série
→
Lista de atalhos
?