URLs amigables, reescritura de URL y reverse proxy
Este documento explica cómo integrar Mindtraining Platform con URLs amigables y cómo configurar reescritura de URL o reverse proxy para que la navegación directa, los marcadores y el refresh funcionen correctamente.
Tabla de contenidos
El problema
Mindtraining Platform usa routing del lado cliente: el navegador carga un único index.html (o equivalente) y JavaScript actualiza la vista según el path de la URL. Cuando un usuario:
Navega directamente a
https://yoursite.com/games/crossword/archiveGuarda en favoritos un deep link
Refresca la página en una ruta distinta de la raíz
…el navegador envía una petición al servidor para ese path exacto. Sin configuración adicional, el servidor busca un archivo en /games/crossword/archive y devuelve 404, porque ese path solo existe en el router SPA, no en disco.
La solución
Todas las peticiones para rutas SPA deben servir el mismo archivo de entrada (index.html o script.js). Después la SPA lee la URL y renderiza la vista correcta. Esto se consigue mediante:
URL rewrite: mapear internamente todas las rutas SPA al archivo de entrada
Reverse proxy: reenviar las peticiones a un backend que sirva la SPA
Fallback / catch-all: tratar cualquier ruta que no sea asset como una ruta SPA
Apache
Usando .htaccess (mod_rewrite)
Coloca esto en el document root o en el subdirectorio de la SPA (por ejemplo, /games/.htaccess):
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /games/
# No reescribir archivos o directorios que existan
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# Reescribir todo lo demás a index.html
RewriteRule ^ index.html [L]
</IfModule>Notas:
Sustituye
/games/por tu base path de la SPA. Usa/si la SPA está en la raíz.Asegúrate de que
mod_rewriteestá habilitado:a2enmod rewrite(Debian/Ubuntu).Debe estar configurado
AllowOverride Allpara que.htaccessse respete.
Usando VirtualHost (sin .htaccess)
<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 en un subdirectorio (por ejemplo /games)
Si la SPA vive bajo /games y tu entrypoint es index.html dentro de esa carpeta:
<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
server {
listen 80;
server_name yoursite.com;
root /var/www/mindtraining;
location / {
try_files $uri $uri/ /index.html;
}
}SPA en un subdirectorio (por ejemplo /games)
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;
}
}Con reverse proxy hacia un origen estático/CDN
Si la SPA se sirve desde un CDN u otro origen:
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 SPA: si el upstream responde 404, servir index
proxy_intercept_errors on;
error_page 404 = /index.html;
}
}Subpath + proxy más robusto
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;
# No reescribir 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 (sin Apache/Nginx)
Si no puedes usar Apache o Nginx (por ejemplo, serverless, PaaS o un servidor Node sencillo), usa uno de estos enfoques.
1. Node.js (Express)
const express = require('express')
const path = require('path')
const app = express()
const PORT = process.env.PORT || 3000
// Servir archivos estáticos (JS, CSS, imágenes)
app.use(express.static(path.join(__dirname, 'dist')))
// Fallback SPA: todas las demás rutas sirven 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 en subdirectorio (por ejemplo /games):
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)
{
"rewrites": [
{ "source": "/(.*)", "destination": "/index.html" }
]
}Para un subpath:
{
"rewrites": [
{ "source": "/games/:path*", "destination": "/games/index.html" }
]
}3. Netlify (_redirects o netlify.toml)
_redirects (en public/ o en la raíz del proyecto):
/* /index.html 200netlify.toml:
[[redirects]]
from = "/*"
to = "/index.html"
status = 200Para el subpath /games:
/games/* /games/index.html 2004. AWS S3 + CloudFront (hosting estático)
S3 no soporta reescrituras. Usa CloudFront Functions o Lambda@Edge:
CloudFront Function (viewer request u origin request):
function handler(event) {
var request = event.request
var uri = request.uri
// No reescribir si parece un archivo
if (uri.includes('.') && !uri.endsWith('.html')) {
return request
}
// Fallback 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)
{
"hosting": {
"public": "dist",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}6. GitHub Pages
GitHub Pages no soporta reescrituras del lado servidor. Opciones:
Usa el truco de 404.html: crea una página 404 personalizada que cargue la SPA y redirija del lado cliente. No es ideal para SEO ni deep links.
Usa un hash router del lado cliente (
#/games/crossword) en vez de routing basado en path. No requiere configuración de servidor, pero las URLs son menos amigables.Aloja la SPA en otro sitio (Vercel, Netlify, etc.) y apunta tu dominio allí.
Particularidades de Mindtraining Platform
Esta plataforma usa TanStack Router con un basepath dinámico obtenido desde la API. Rutas típicas:
| Patrón de path | Ejemplo |
|---|---|
| Home | / o /{basepath}/ |
| Juego de hoy | /{basepath}/crossword/ |
| Archivo | /{basepath}/crossword/archive |
| Estadísticas | /{basepath}/crossword/statistics |
| Fecha concreta | /{basepath}/crossword/2024-03-18 |
El basepath se configura por sitio/dominio. Asegúrate de que tus reglas cubren el basepath completo. Por ejemplo, si basepath es /games:
Apache:
RewriteBase /games/y servirindex.htmlpara todas las peticiones que no sean archivos bajo/gamesNginx:
location /games { try_files $uri $uri/ /games/index.html; }Express: montar estáticos + fallback bajo
/games