Skip to content

URLs conviviales, réécriture d'URL et reverse proxy

Ce document explique comment intégrer Mindtraining Platform avec des URLs conviviales et comment configurer la réécriture d'URL ou un reverse proxy pour que la navigation directe, les favoris et le rafraîchissement fonctionnent correctement.


Table des matières


Le problème

Mindtraining Platform utilise un routage côté client : le navigateur charge un unique index.html (ou équivalent) et JavaScript met à jour la vue en fonction du chemin de l'URL. Quand un utilisateur :

  • Accède directement à https://yoursite.com/games/crossword/archive

  • Met en favori un deep link

  • Rafraîchit la page sur une route autre que la racine

…le navigateur envoie une requête au serveur pour ce chemin exact. Sans configuration, le serveur cherche un fichier à /games/crossword/archive et renvoie 404, car ce chemin n'existe que dans le routeur SPA, pas sur disque.


La solution

Toutes les requêtes visant des routes SPA doivent servir le même fichier d'entrée (index.html ou script.js). La SPA lit ensuite l'URL et affiche la bonne vue. Cela se fait via :

  1. URL rewrite — mapper en interne toutes les routes SPA vers le fichier d'entrée

  2. Reverse proxy — transférer les requêtes vers un backend qui sert la SPA

  3. Fallback / catch-all — traiter toute route non statique comme une route SPA


Apache

Avec .htaccess (mod_rewrite)

Placez ceci dans la racine du document ou dans le sous-répertoire de la SPA (par ex. /games/.htaccess) :

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

  # Ne pas réécrire les fichiers ou dossiers existants
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d

  # Réécrire tout le reste vers index.html
  RewriteRule ^ index.html [L]
</IfModule>

Notes :

  • Remplacez /games/ par votre base path SPA. Utilisez / si la SPA est à la racine.

  • Vérifiez que mod_rewrite est activé : a2enmod rewrite (Debian/Ubuntu).

  • AllowOverride All doit être défini pour que .htaccess soit pris en compte.

Avec VirtualHost (sans .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 dans un sous-répertoire (par ex. /games)

Si la SPA vit sous /games et que votre point d'entrée est index.html dans ce dossier :

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 SPA basique

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

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

SPA dans un sous-répertoire (par ex. /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;
  }
}

Avec reverse proxy vers une origine statique/CDN

Si la SPA est servie depuis un CDN ou une autre origine :

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 SPA : si l'upstream répond 404, servir index
    proxy_intercept_errors on;
    error_page 404 = /index.html;
  }
}

Sous-chemin + proxy plus robuste

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;

    # Ne pas réécrire les assets statiques
    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;
  }
}

Alternatives (sans Apache/Nginx)

Si vous ne pouvez pas utiliser Apache ou Nginx (par ex. serverless, PaaS ou simple serveur Node), utilisez l'une des approches suivantes.

1. Node.js (Express)

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

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

// Servir les fichiers statiques (JS, CSS, images)
app.use(express.static(path.join(__dirname, 'dist')))

// Fallback SPA : toutes les autres routes servent 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 dans un sous-répertoire (par ex. /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" }
  ]
}

Pour un sous-chemin :

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

3. Netlify (_redirects ou netlify.toml)

_redirects (dans public/ ou à la racine du projet) :

txt
/*    /index.html   200

netlify.toml :

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

Pour le sous-chemin /games :

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

4. AWS S3 + CloudFront (hébergement statique)

S3 ne prend pas en charge les rewrites. Utilisez CloudFront Functions ou Lambda@Edge :

CloudFront Function (viewer request ou origin request) :

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

  // Ne pas réécrire si cela ressemble à un fichier
  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)

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

6. GitHub Pages

GitHub Pages ne prend pas en charge les rewrites côté serveur. Options :

  • Utiliser l'astuce 404.html : créer une page 404 personnalisée qui charge la SPA et redirige côté client. Ce n'est pas idéal pour le SEO ni pour les deep links.

  • Utiliser un hash router côté client (#/games/crossword) au lieu d'un routage basé sur le path. Aucune config serveur n'est nécessaire, mais les URLs sont moins propres.

  • Héberger la SPA ailleurs (Vercel, Netlify, etc.) et pointer votre domaine vers cet hébergement.


Spécificités de Mindtraining Platform

Cette plateforme utilise TanStack Router avec un basepath dynamique venant de l'API. Routes typiques :

Pattern de routeExemple
Accueil/ ou /{basepath}/
Jeu du jour/{basepath}/crossword/
Archive/{basepath}/crossword/archive
Statistiques/{basepath}/crossword/statistics
Date spécifique/{basepath}/crossword/2024-03-18

Le basepath est configuré par site/domaine. Assurez-vous que vos règles couvrent tout le basepath. Par exemple, si basepath vaut /games :

  • Apache : RewriteBase /games/ et servir index.html pour toutes les requêtes non fichier sous /games

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

  • Express : monter les assets statiques + le fallback sous /games


Client integration documentation maintained in-repo.