Como corrigir o erro 404 no Angular ao efetuar o refresh ou acessar rotas diretamente do navegador

O erro 404 no Angular surge quando o servidor web não está configurado para lidar com o roteamento client-side do Angular. Aprenda a corrigir esse problema no Nginx e Apache

Como corrigir o erro 404 no Angular ao efetuar o refresh ou acessar rotas diretamente do navegador
Como corrigir o erro 404 no Angular ao efetuar o refresh ou acessar rotas diretamente do navegador

Se você trabalha com projetos que usam o framework Angular, talvez tenha enfrentado um problema comum: tudo funciona perfeitamente durante o desenvolvimento. No entanto, após implantar o projeto em produção, ao navegar entre as rotas, você percebe que, ao atualizar a página ou tentar acessar uma rota diretamente na barra de endereços, surge o erro 404 Not Found. Neste texto, vamos entender por que isso acontece e como resolver esse problema.

Servindo arquivos estáticos para web

Arquivos estáticos, como HTML, CSS, JavaScript e imagens, são a base de muitos sites e aplicações web, incluindo projetos Angular. Servi-los de forma eficiente é crucial para garantir uma experiência rápida, confiável e segura para os usuários.

Nesse contexto, softwares como Nginx, Apache e serviços de hospedagem estática como GitHub Pages se destacam por sua simplicidade, performance e escalabilidade. Ao invés de processar cada requisição dinamicamente, esses servidores entregam os arquivos estáticos diretamente aos usuários, reduzindo a carga do servidor e otimizando o tempo de resposta.

Para projetos Angular, essa abordagem é particularmente vantajosa, pois após compilarmos o projeto, o resultado é composto por arquivos estáticos (html, js, css e imagens). 

Diagrama de sequência ilustrando o Nginx servindo arquivos estáticos
Diagrama ilustrando o Nginx servindo arquivos estáticos

Dito isso, vamos criar um cenário de estudo envolvendo um projeto Angular com algumas rotas. Em seguida, vamos servir os arquivos através do Nginx e Apache para explorar a causa do erro 404 Not Found. Por fim, faremos um ajuste para resolver esse problema.

Cenário de estudo: criando o projeto Angular

Parar criar o projeto Angular:

# Conferindo a versão do Angular/CLI
> ng version

Angular CLI: 18.1.1
Node: 20.12.0
Package Manager: npm 10.8.1
OS: darwin arm64

# Criando o projeto
> ng new \
--skip-git \
--skip-tests \
--standalone \
--strict \
--minimal \
--inline-template \
--routing \
exemplo404
💡
Utilize o comando ng new help para visualizar os parâmetros do comando ng new

Adicionando rotas ao projeto

Com o projeto criado, vamos adicionar 2 componentes, um para cada rota:

# Criando os componentes:
#
# Certifique-se de estar no diretório do projeto.
# Use o comando pwd para verificar seu diretório atual.
#
> ng g c rota1 --skip-tests --style none
> ng g c rota2 --skip-tests --style none

Mapeando as rotas:

import { Routes } from "@angular/router";
import { Rota1Component } from "./rota1/rota1.component";
import { Rota2Component } from "./rota2/rota2.component";

export const routes: Routes = [
  {
    path: "",
    component: Rota1Component,
  },
  {
    path: "rota2",
    component: Rota2Component,
  },
];

app.routes.ts

import { Component } from '@angular/core';
import { RouterModule } from '@angular/router';

@Component({
  standalone: true,
  imports: [RouterModule],
  template: `
    <p>
      rota1 works!
    </p>
    <button [routerLink]="['/rota2']">
      ir para rota 2
    </button>
  `,
  styles: ``
})
export class Rota1Component {

}

rota1.component.ts

import { Component } from '@angular/core';
import { RouterModule } from '@angular/router';

@Component({
  standalone: true,
  imports: [RouterModule],
  template: `
    <p>
      rota2 works!
    </p>
    <button [routerLink]="['/']">
      ir para rota 1
    </button>
  `,
  styles: ``
})
export class Rota2Component {

}

rota2.component.ts

Efetuando um teste em modo de desenvolvimento

Com o projeto configurado, vamos testá-lo em modo de desenvolvimento executando o comando npm run start:

Projeto Angular sendo executado no navegador
Projeto Angular em execução

Veja no GIF acima que podemos mudar a rota diretamente na barra de endereços do navegador e atualizar a página sem problemas. Tudo funciona perfeitamente no modo de desenvolvimento.

Compilando o projeto

Visto que o projeto está funcionando corretamente, vamos efetuar o build do projeto com o comando npm run build. Após o processamento, o resultado do build estará no diretório /dist/exemplo404/browser deste exemplo.

Lista de arquivos gerados após a compilação do projeto
Arquivos gerados após a compilação do projeto

Testando o projeto sendo servido pelo Nginx

Para simular o erro 404, vamos servir os arquivos gerados anteriormente usando o Nginx.

Para evitar a instalação do Nginx no meu computador, usei um container Docker. Utilizei o comando abaixo para criar um container Docker a partir de uma imagem do Nginx:

> docker run \
--name nginx-angular \
-v /<dir do projeto>/dist/exemplo404/browser:/usr/share/nginx/html:ro \
-p 8080:80 \
-d nginx

Explicação do comando:

  • --name
    Nome do container
  • -v
    Vincula um diretório do meu computador (host) com um diretório dentro do container.
  • -p:
    Vincula uma porta no meu computador (host) com uma porta do container.
  • -d:
    Nome da imagem
💡
Para explorar mais imagens Docker, acesse: http://hub.docker.com

Erro 404 Not Found

Após executar o comando acima, o container do Nginx estará em execução, então podemos abrir o navegador e acessar o endereço http://localhost:8080 para carregar a aplicação Angular.

Nexte contexto, se acessarmos localhost:8080/favicon.ico ou localhost:8080/main-XZS6USHQ.js, o Nginx retornará o conteúdo desses arquivos corretamente. No entanto, se tentarmos acessar localhost:8080/rota2, o Nginx devolverá o erro 404 Not Found. Isso ocorre porque o Nginx não sabe como lidar com essa rota. Ele tenta encontrar um arquivo ou diretório chamado rota2 e, como não existe, retorna o erro 404.

Observação: Lembre-se de que configuramos o Nginx para usar o diretório com os arquivos do projeto de exemplo (veja a imagem abaixo). Esses são os arquivos estáticos que o Nginx pode servir:

Lista de arquivos gerados na compilação do projeto
Arquivos gerados na compilação do projeto

Por outro lado, se você acessar localhost:8080 ou localhost:8080/index.html, o Nginx retornará o conteúdo do arquivo index.html, que é o ponto de entrada da aplicação Angular. Uma vez que a aplicação Angular é carregada e interpretada pelo navegador, ela gerencia as rotas internamente. Isso significa que você pode navegar entre as rotas usando links ou botões dentro da própria aplicação.

Veja no GIF abaixo que a navegação pelos botões funciona perfeitamente. Porém, se usarmos a barra de endereços do navegador ou atualizarmos a página em uma rota diferente da raiz, aparece o erro 404.

Aplicação Angular em execução no navegador mostrando o erro 404 Not Found
Exemplo do 404 Not Found

Resolvendo o erro 404 Not Found no Nginx

Para resolver isso, é necessário configurar o Nginx para sempre servir o arquivo index.html para qualquer rota desconhecida. Desta forma, quando o navegador carregar o index.html, o contexto do Angular será criado, permitindo que o Angular gerencie as rotas após o carregamento inicial.

No caso do Nginx, existe um arquivo de configuração chamado nginx.conf onde podemos configurar esse comportamento. Para obter o conteúdo desse arquivo podemos executar os seguintes comandos:

# Acessando o terminal do container
> docker exec -it nginx-angular /bin/bash

# Obtendo o conteúdo do arquivo:
> cat /etc/nginx/nginx.conf

Com o conteúdo do arquivo nginx.conf em mãos, criei um arquivo com o mesmo nome dentro do diretório do projeto Angular (o local é opcional) e fiz a seguinte modificação:

user nginx;
worker_processes auto;

# (linhas ocultadas...)

http {
 # (linhas ocultadas...)

 # Comentei a linha abaixo e substituí
 # pelo conteúdo a seguir
 # include /etc/nginx/conf.d/*.conf;
 server {
  listen 80;
  listen [::]:80;
  server_name localhost;

  location / {
   root /usr/share/nginx/html;
   index index.html index.htm;

   # Quando o arquivo não for encontrado,
   # irá devolver o index.html
   try_files $uri $uri/ /index.html;
  }

  # (linhas ocultadas...)
 }
}

nginx.conf

Finalmente removi o container que está em execução e recriei incluindo a configuração do Nginx customizada logo acima:

# Interrompendo a execução do container
> docker container stop nginx-angular

# Removendo o container
> docker container rm nginx-angular

# Criando um novo container
> docker run \
--name nginx-angular \
-v /<dir do projeto>/dist/exemplo404/browser:/usr/share/nginx/html:ro \
-v /<dir do projeto>/nginx.conf:/etc/nginx/nginx.conf:ro \
-p 8080:80 \
-d nginx

Resultado:

Aplicação Angular em execução no navegador sem apresentar o erro 404 Not Found
Resultado após o ajuste no Nginx
Diagrama ilustrando em que momento o contexto do Angular é inicializado
Diagrama ilustrando em que momento o contexto do Angular é inicializado

Testando o projeto sendo servido pelo Apache

De forma similar ao Nginx, mas agora com o Apache, segui as seguintes etapas:

Criando o container Docker do Apache

Primeiramente criei um container com a imagem do Apache com o comando abaixo:

docker run \
--name apache-angular \
-p 9090:80 \
-v /<dir do projeto>/dist/exemplo404/browser:/usr/local/apache2/htdocs:ro \
-d httpd:2.4

Obtendo o arquivo httpd.conf

Assim como no Nginx, neste momento o container em execução apresenta o mesmo problema do 404. Então de forma similar ao Nginx, precisamos obter o conteúdo do arquivo de configuração do Apache para aplicar uma modificação. Para isto acessei o terminal do container com os seguintes comandos:

> docker exec -it "apache-angular" /bin/bash

# A partir daqui estamos no console
# do container
#
# Obtendo o conteúdo do arquivo httpd.conf:
#
> cat /usr/local/apache2/conf/httpd.conf

Modificando o arquivo de configuração httpd.conf

Então criei um arquivo chamado httpd.conf no diretório do meu projeto com as seguintes modificações:

ServerRoot "/usr/local/apache2"
Listen 80

# ...(linhas ocultadas)
LoadModule rewrite_module modules/mod_rewrite.so

# ...(linhas ocultadas)
DocumentRoot "/usr/local/apache2/htdocs"
<Directory "/usr/local/apache2/htdocs">
    # ...(linhas ocultadas)

    <IfModule mod_rewrite.c>
        RewriteEngine On
        RewriteBase /
        RewriteRule ^index\.html$ - [L]
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteCond %{REQUEST_FILENAME} !-d
        RewriteRule . /index.html [L]
    </IfModule>
</Directory>

# ...(linhas ocultadas)

httpd.conf

Recriando o container

Para finalizar executei os comandos abaixo:

# Para a execução do container
> docker container stop "apache-angular"

# Remove o container
> docker container rm "apache-angular"

# Cria o container
> docker run \
--name apache-angular \
-p 9090:80 \
-v /<dir do projeto>/dist/exemplo404/browser:/usr/local/apache2/htdocs:ro \
-v /<dir do projeto>/httpd.conf:/usr/local/apache2/conf/httpd.conf:ro \
-d httpd:2.4

Resultado:

Projeto Angular em execução no navegador sendo servido pelo Apache
Projeto em execução sendo servido pelo Apache

Considerações

Neste texto, falamos sobre o erro 404 que surge ao atualizar ou acessar rotas client-side diretamente no navegador em aplicações Angular. Abordamos as soluções envolvendo o Nginx e Apache, configurando para entregar o arquivo principal da aplicação (index.html) quando uma rota não for encontrada.

Outros frameworks ou bibliotecas como React, seguem o mesmo princípio do Angular no roteamento client-side. Então ao configurar o servidor de arquivos estáticos para entregar o index.html quando uma rota não for encontrada, a aplicação poderá assumir o controle do roteamento e exibir o conteúdo correto.