Deploy completo de SPA com AWS S3 — Guia detalhado
Autor
Caroline Oliveira
Data de publicação

Visão geral
Uma Single Page Application (SPA) funciona com uma única página HTML: o navegador carrega a index.html e todo o restante (rotas, carregamento de componentes, fetch de dados etc.) acontece no lado do cliente, via JavaScript.
Em aplicações multipágina, cada URL costuma corresponder a um arquivo no servidor. Em uma SPA, todas as rotas (ex.: /sobre, /contato) precisam, na prática, "cair" na mesma index.html, e o roteador do front (ex.: React Router, Vue Router) decide o que exibir.
Por isso, ao fazer deploy em um bucket S3, é essencial configurar o ambiente para sempre devolver a index.html em qualquer requisição aos possíveis paths da aplicação, inclusive em erros 404. Caso contrário, ao acessar uma rota diretamente ou dar refresh, o S3 tentaria buscar um arquivo com aquele caminho e retornaria erro.
Características do projeto de exemplo
- Framework/build: Vite (ou qualquer gerador de SPA que produza arquivos estáticos).
- Saída do build: arquivos na pasta dist (ou equivalente).
- Hospedagem: um bucket S3
Stack utilizada
- AWS S3 — armazenamento dos arquivos estáticos.
- Amazon CloudFront — CDN e ponto único de entrada
Configuração da Stack
Configuração do bucket S3
No console da AWS, abra o S3 e crie ou selecione o bucket.
- Em Block Public Access (bucket settings):
- Desmarque "Block all public access" (ou ajuste as opções para permitir acesso público somente aos objetos que você vai servir).
- Sem isso, nem o CloudFront nem usuários finais conseguirão acessar os arquivos e o site fica indisponível.

No bucket já criado, vá em Properties.
- Role até Static website hosting e clique em Edit.
- Selecione Enable.
- Em Hosting type, escolha Host a static website.
- Index document: index.html.
- Error document: index.html (fundamental para SPA: qualquer rota ou 404 deve devolver a index.html; o JS cuida do roteamento).
- Salve. Anote a URL do endpoint (algo como http://nome-do-bucket.s3-website-regiao.amazonaws.com). Você usará essa URL como origin no CloudFront, não a URL do tipo s3.amazonaws.com.

Páginas de índice e de erro
- Index document e Error document devem ser ambos index.html.
- Isso garante que rotas como /sobre ou /produtos/123 não resultem em 404 do S3, e sim na sua aplicação, que então trata a rota no cliente.
Policy do bucket (para o CloudFront acessar)
- No bucket, vá em Permissions → Bucket policy → Edit.
- Use uma policy que permita leitura pública dos objetos (ou restrita ao CloudFront, se preferir usar OAC/legacy OAI).
Exemplo para acesso público de leitura:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "PublicReadGetObject", "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::SEU-NOME-DO-BUCKET/*" } ]}Substitua SEU-NOME-DO-BUCKET pelo nome real do bucket e salve a policy.
Configurando a distribuição do CloudFront
Origin
- No CloudFront, crie uma Distribution.
Em Origin:
- Não use a URL do tipo bucket.s3.amazonaws.com. Use a URL do Static website hosting do S3 (ex.: http://nome-do-bucket.s3-website-regiao.amazonaws.com). Isso evita problemas de roteamento e de página de erro que ocorrem quando se usa o endpoint REST do S3 para site estático.
Página de erro (Custom Error Response)
Em Error pages (Custom error responses):
- Adicione uma resposta customizada para o código 403 (e, se quiser, 404).
- Response page path: /index.html.
- HTTP response code: 200 (para que o cliente receba 200 e carregue a SPA).
Assim, qualquer URL que o CloudFront não encontre como arquivo físico será atendida com a index.html e sua SPA assume o roteamento.

Testando
Acesse a URL da distribuição do CloudFront (ou o domínio customizado). Navegar por links internos e abrir rotas diretamente (ou dar F5) deve manter a aplicação funcionando, sempre exibindo a index.html e deixando o roteamento a cargo do JavaScript.
Próximos passos
Configurar um subdomínio apontando para o CloudFront (CNAME no DNS).
Automatizar o deploy com GitHub Actions (build + upload para o S3 + invalidação do CloudFront) em cada push na branch main.
Em próximos posts será detalhado o passo a passo de subdomínio, certificado e CI/CD para esse fluxo.