Objetivos de aprendizado
Depois de ler este artigo, você vai conseguir:
- Entender por que recarregar quebra rotas que funcionavam na navegação
- Aplicar o fallback para o index.html no nginx, Express, Apache ou Netlify
- Evitar que páginas 404 reais e arquivos faltando fiquem escondidos
Você faz o deploy do seu app React ou Vue. A página inicial carrega, os links funcionam, e aí você recarrega em /dashboard ou cola um link direto e recebe um 404 do servidor. Na sua máquina isso nunca aconteceu.
O app está certo. O servidor é que não sabe que /dashboard é uma tela do seu JavaScript, e não um arquivo. A seguir, o mecanismo e a correção para os servidores mais comuns.
Por que recarregar dá 404 se a navegação funciona?#
Uma single-page application (SPA) entrega um único arquivo HTML, normalmente o index.html, mais os pacotes de JavaScript. Quando você clica num link, o roteador (React Router, Vue Router) usa a History API e troca a tela. A URL muda, mas o navegador não envia nenhuma requisição.
Recarregar é diferente. O navegador pede ao servidor exatamente aquele caminho:
GET /dashboard HTTP/1.1
Host: example.comUma hospedagem estática associa URLs a arquivos. Não existe arquivo nem pasta dashboard na saída do build, então ela responde:
404 Not FoundO servidor de desenvolvimento esconde isso porque já faz fallback para o index.html em caminhos desconhecidos. A documentação do Vite também avisa que o vite preview serve só para visualizar o build localmente e não foi feito para ser um servidor de produção, então um preview funcionando prova pouco sobre a sua hospedagem.
Como corrigir no servidor?#
Configure o servidor para devolver o index.html em qualquer caminho que não seja um arquivo existente. Assim o navegador carrega o app, o roteador lê a URL e mostra a tela certa. A documentação do Vue Router descreve isso como a configuração de servidor obrigatória no modo history.
A regra tem duas partes, nesta ordem:
- Se a requisição corresponde a um arquivo real (um script, uma folha de estilo, uma imagem), sirva o arquivo.
- Caso contrário, sirva o
index.htmle deixe o roteador do cliente decidir.
Veja essa regra nos servidores mais comuns.
nginx#
Use try_files no bloco location /:
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}O nginx tenta o arquivo e depois o diretório. Se nenhum dos dois existir, vale o último parâmetro, que aqui é um redirecionamento interno para /index.html. Recarregue o nginx depois de editar.
Express ou outro servidor Node.js#
Sirva a pasta do build e depois acrescente uma rota curinga que envia o index.html. Este exemplo usa o Express 5, que exige um curinga com nome:
import express from "express";
import path from "node:path";
import { fileURLToPath } from "node:url";
const dist = path.join(path.dirname(fileURLToPath(import.meta.url)), "dist");
const app = express();
app.use(express.static(dist));
app.get("/{*splat}", (req, res) => {
res.sendFile(path.join(dist, "index.html"));
});
app.listen(process.env.PORT || 3000);No Express 4 a rota curinga é app.get("*", ...). No Express 5 o curinga precisa ter nome, e /{*splat} também casa com o caminho raiz, como explica o guia de migração do Express. Registre a rota curinga depois do express.static e das rotas da sua API, senão ela engole essas rotas.
Apache#
Coloque isto num arquivo .htaccess ao lado do index.html, com o mod_rewrite ativado:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.html [L]
</IfModule>As duas condições pulam arquivos e diretórios existentes, então só os caminhos desconhecidos caem no fallback.
Netlify e Firebase Hosting#
Na Netlify, adicione um arquivo _redirects na pasta publicada (num app Vite, coloque em public/ para ele ser copiado para dist/):
/* /index.html 200O 200 transforma a regra numa reescrita, e não num redirecionamento, então a URL continua a mesma. Por padrão, a Netlify não sobrepõe URLs que correspondem a um arquivo real, então os arquivos continuam sendo servidos normalmente.
No Firebase Hosting, adicione uma reescrita no firebase.json (arquivos que existem continuam sendo servidos primeiro):
{
"hosting": {
"public": "dist",
"rewrites": [
{ "source": "**", "destination": "/index.html" }
]
}
}Outras plataformas têm uma configuração equivalente, muitas vezes chamada de "SPA mode", "rewrites" ou "fallback". Procure essas palavras na documentação da sua hospedagem.
Por que agora aparece uma página em branco ou um erro de MIME type?#
Um fallback amplo demais causa o problema oposto. Se o navegador pede /assets/app.abc123.js e o arquivo não existe, o servidor devolve o index.html com status 200, e o navegador recebe HTML onde esperava JavaScript. Com scripts de módulo, que é o que o Vite gera, o Chrome mostra o primeiro erro abaixo; com scripts clássicos, o segundo:
Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "text/html".
Uncaught SyntaxError: Unexpected token '<'Causas comuns:
- Caminho base errado. O app é servido num subcaminho (
/app/), mas o build foi feito para/. Ajuste obaseno Vite. - URLs relativas de arquivos em rotas profundas. Em
/users/42, umsrc="assets/app.js"vira/users/assets/app.js. Use caminhos absolutos começando com/. - Deploy desatualizado. O
index.htmlem cache aponta para arquivos com hash do build anterior, que não existem mais. Envie os arquivos novos junto com oindex.htmlnovo e evite cache longo noindex.html.
Abra a aba Network e confira a requisição que falha. Se um arquivo .js volta como text/html, o fallback está pegando um arquivo que falta.
Como manter uma página 404 de verdade?#
Com um fallback amplo, o servidor nunca devolve 404 para páginas. A documentação do Vue Router aponta essa consequência e sugere uma rota curinga dentro da aplicação:
// Vue Router
{ path: "/:pathMatch(.*)", component: NotFound }// React Router
<Route path="*" element={<NotFound />} />O usuário vê uma tela de página não encontrada, mas o servidor continua respondendo 200, e buscadores podem tratar essas páginas como soft 404. Se isso importa, adicione uma meta tag noindex na tela de página não encontrada, ou passe para renderização no servidor ou pré-renderização, para que o servidor possa devolver um 404 real. Veja os prós e contras em SSR vs SSG vs estático.
E o HashRouter?#
O HashRouter (React) e o createWebHashHistory (Vue) colocam a rota depois do #, como em /#/dashboard. O navegador nunca envia o fragmento ao servidor, então todo recarregamento pede / e nenhuma configuração de servidor é necessária. É uma opção legítima para hospedagens em que você não consegue adicionar reescritas. A desvantagem é que as URLs ficam piores e o servidor não enxerga nem indexa as rotas individuais.
Como fazer isso na Square Cloud#
A Square Cloud é hospedagem self-service, então você decide como o build é servido. No runtime de sites estáticos, o fallback é um passo só: renomeie o index.html do build para vite.html. O runtime responde com o vite.html a todo caminho que não é um arquivo, então qualquer link direto carrega o app. Sem vite.html, caminhos desconhecidos recebem o seu 404.html.
Se preferir controlar o roteamento você mesmo, use o runtime de Node.js com um servidor Express como o de cima (com express no seu package.json). As duas opções estão no tutorial de React da documentação. Uma aplicação web precisa de pelo menos 512 MB de RAM do pool compartilhado do seu plano e ganha um subdomínio *.squareweb.app com HTTPS. O deploy pode ser por upload de ZIP no painel, pela CLI ou pelo GitHub.
Depois do deploy, abra um link direto e recarregue a página para confirmar a correção. O caminho completo do build até a URL no ar está em como hospedar um app React, e os recursos para sites estão na página de Sites.
Hospedagem de sites
Sites estáticos e SSR com TLS automático e CDN global
Perguntas frequentes
Por que minha SPA funciona na página inicial mas dá 404 ao recarregar?
Clicar num link muda a URL no navegador sem fazer requisição, e o roteador cuida disso. Recarregar envia uma requisição de verdade para aquele caminho, e um servidor estático não tem arquivo nele.
O HashRouter resolve?
Sim. A rota fica depois do # na URL, que o navegador nunca envia ao servidor, então todo recarregamento pede o mesmo index.html. O preço são URLs mais feias, e o servidor não enxerga a rota.
O fallback deve responder com status 200 ou 404?
Numa SPA renderizada no cliente, 200 com o index.html, porque é a aplicação que decide o que mostrar. Mostre uma tela de página não encontrada dentro da aplicação para rotas desconhecidas.
Por que aparece uma página em branco ou um erro de MIME type em vez de 404?
Um fallback amplo demais também responde com o index.html a pedidos de arquivos JavaScript ou CSS que não existem. O navegador recebe HTML onde esperava um script. Confira os caminhos dos arquivos e a configuração de base.
Mais em Sites
Ver todos os 20 artigos- O que é um site estático e onde hospedarSite estático é um conjunto de arquivos HTML, CSS e JavaScript prontos, entregues iguais a todos os visitantes. Veja como funciona, quando usar e onde hospedar.
- Quanto custa hospedar um site?Hospedar um site custa de zero a algumas dezenas de dólares por mês. Veja o que entra na conta: hospedagem, domínio, tráfego e extras, com exemplos e limites.
- Quanto custa hospedar um site estáticoHospedar um site estático custa de zero a algumas dezenas de dólares por mês na maioria dos casos. Veja como o tráfego define o preço, com a conta passo a passo.
- Quanto custa hospedar um app Next.jsHospedar um app Next.js custa de zero, com export estático, a dezenas de dólares por mês com servidor. Veja como cada modelo cobra e os custos escondidos.