Skip to content

URLs amigáveis, reescrita de URL e proxy reverso

Este documento explica como integrar o Mindtraining Platform com URLs amigáveis e como configurar reescrita de URL ou proxy reverso para que navegação direta, favoritos e refresh funcionem corretamente.


Sumário


O problema

O Mindtraining Platform usa roteamento client-side: o navegador carrega um único index.html (ou equivalente) e o JavaScript atualiza a visualização com base no caminho da URL. Quando um usuário:

  • Navega diretamente para https://yoursite.com/games/crossword/archive

  • Salva um favorito com deep link

  • Atualiza a página em uma rota que não seja a raiz

…o navegador envia uma requisição ao servidor para aquele caminho exato. Sem configuração, o servidor procura um arquivo em /games/crossword/archive e retorna 404, porque esse caminho existe apenas no roteador da SPA, não em disco.


A solução

Todas as requisições para rotas da SPA precisam servir o mesmo arquivo de entrada (index.html ou script.js). A SPA então lê a URL e renderiza a view correta. Isso é feito por meio de:

  1. URL rewrite — mapear internamente todos os caminhos da SPA para o arquivo de entrada

  2. Proxy reverso — encaminhar as requisições para um backend que sirva a SPA

  3. Fallback / catch-all — tratar qualquer caminho que não seja asset como rota da SPA


Apache

Usando .htaccess (mod_rewrite)

Coloque isso no document root ou no subdiretório da SPA (por exemplo, /games/.htaccess):

apache
<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /games/

  # Não reescrever arquivos ou diretórios existentes
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d

  # Reescrever todo o restante para index.html
  RewriteRule ^ index.html [L]
</IfModule>

Notas:

  • Substitua /games/ pelo base path da sua SPA. Use / se a SPA estiver na raiz.

  • Garanta que mod_rewrite está habilitado: a2enmod rewrite (Debian/Ubuntu).

  • AllowOverride All precisa estar configurado para que o .htaccess seja respeitado.

Usando VirtualHost (sem .htaccess)

apache
<VirtualHost *:80>
  ServerName yoursite.com
  DocumentRoot /var/www/mindtraining

  <Directory /var/www/mindtraining>
    Options -Indexes +FollowSymLinks
    AllowOverride None
    Require all granted

    RewriteEngine On
    RewriteBase /games/
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^ index.html [L]
  </Directory>
</VirtualHost>

SPA em um subdiretório (por exemplo /games)

Se a SPA vive em /games e o entrypoint é index.html nessa pasta:

apache
<Directory /var/www/html/games>
  RewriteEngine On
  RewriteBase /games/
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule ^ /games/index.html [L]
</Directory>

Nginx

Fallback básico para SPA

nginx
server {
  listen 80;
  server_name yoursite.com;
  root /var/www/mindtraining;

  location / {
    try_files $uri $uri/ /index.html;
  }
}

SPA em um subdiretório (por exemplo /games)

nginx
server {
  listen 80;
  server_name yoursite.com;
  root /var/www/html;

  location /games {
    alias /var/www/html/games;
    try_files $uri $uri/ /games/index.html;
  }
}

Com proxy reverso para uma origem estática/CDN

Se a SPA é servida a partir de um CDN ou outra origem:

nginx
server {
  listen 80;
  server_name yoursite.com;

  location / {
    proxy_pass <https://cdn.example.com/mindtraining/>;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # Fallback da SPA: se o upstream responder 404, servir index
    proxy_intercept_errors on;
    error_page 404 = /index.html;
  }
}

Subpath + proxy mais robusto

nginx
server {
  listen 80;
  server_name yoursite.com;

  location /games/ {
    proxy_pass <https://cdn.example.com/mindtraining/>;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # Não reescrever assets estáticos
    proxy_intercept_errors on;
    proxy_next_upstream error timeout http_404;
    error_page 404 = @spa_fallback;
  }

  location @spa_fallback {
    rewrite ^ /games/index.html break;
    proxy_pass <https://cdn.example.com/mindtraining/>;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
  }
}

Alternativas (sem Apache/Nginx)

Se você não pode usar Apache ou Nginx (por exemplo, serverless, PaaS ou um servidor Node simples), use uma destas abordagens.

1. Node.js (Express)

js
const express = require('express')
const path = require('path')

const app = express()
const PORT = process.env.PORT || 3000

// Servir arquivos estáticos (JS, CSS, imagens)
app.use(express.static(path.join(__dirname, 'dist')))

// Fallback da SPA: todas as outras rotas servem index.html
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, 'dist', 'index.html'))
})

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`)
})

SPA em subdiretório (por exemplo /games):

js
const express = require('express')
const path = require('path')

const app = express()
const BASE = '/games'

app.use(BASE, express.static(path.join(__dirname, 'dist')))

app.get(`${BASE}/*`, (req, res) => {
  res.sendFile(path.join(__dirname, 'dist', 'index.html'))
})

app.listen(process.env.PORT || 3000)

2. Vercel (vercel.json)

json
{
  "rewrites": [
    { "source": "/(.*)", "destination": "/index.html" }
  ]
}

Para um subpath:

json
{
  "rewrites": [
    { "source": "/games/:path*", "destination": "/games/index.html" }
  ]
}

3. Netlify (_redirects ou netlify.toml)

_redirects (em public/ ou na raiz do projeto):

txt
/*    /index.html   200

netlify.toml:

toml
[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

Para o subpath /games:

txt
/games/*    /games/index.html    200

4. AWS S3 + CloudFront (hospedagem estática)

O S3 não suporta rewrites. Use CloudFront Functions ou Lambda@Edge:

CloudFront Function (viewer request ou origin request):

js
function handler(event) {
  var request = event.request
  var uri = request.uri

  // Não reescrever se parecer um arquivo
  if (uri.includes('.') && !uri.endsWith('.html')) {
    return request
  }

  // Fallback da SPA
  if (!uri.endsWith('/') && !uri.includes('.')) {
    request.uri = '/index.html'
  } else if (uri.endsWith('/')) {
    request.uri = uri + 'index.html'
  }

  return request
}

5. Firebase Hosting (firebase.json)

json
{
  "hosting": {
    "public": "dist",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

6. GitHub Pages

O GitHub Pages não suporta rewrites no lado do servidor. Opções:

  • Use o truque do 404.html: crie uma página 404 personalizada que carregue a SPA e faça o redirecionamento no cliente. Não é ideal para SEO nem para deep links.

  • Use um hash router no cliente (#/games/crossword) em vez de roteamento baseado em path. Não requer configuração de servidor, mas as URLs ficam menos amigáveis.

  • Hospede a SPA em outro lugar (Vercel, Netlify etc.) e aponte o domínio para lá.


Especificidades do Mindtraining Platform

Esta plataforma usa TanStack Router com um basepath dinâmico vindo da API. Rotas típicas:

Padrão de pathExemplo
Home/ ou /{basepath}/
Jogo de hoje/{basepath}/crossword/
Arquivo/{basepath}/crossword/archive
Estatísticas/{basepath}/crossword/statistics
Data específica/{basepath}/crossword/2024-03-18

O basepath é configurado por site/domínio. Garanta que as suas regras cubram o basepath completo. Por exemplo, se basepath for /games:

  • Apache: RewriteBase /games/ e servir index.html para todas as requisições que não sejam arquivos dentro de /games

  • Nginx: location /games { try_files $uri $uri/ /games/index.html; }

  • Express: montar estáticos + fallback em /games


Client integration documentation maintained in-repo.