Skip to content

Friendly URLs, URL Rewrite & Reverse Proxy ​

This document explains how to integrate Mindtraining Platform with friendly URLs and configure URL rewriting or reverse proxy so that direct navigation, bookmarks, and refresh work correctly.


Table of Contents ​


The Problem ​

Mindtraining Platform use client-side routing: the browser loads a single index.html (or equivalent) and JavaScript updates the view based on the URL path. When a user:

  • Navigates directly to https://yoursite.com/games/crossword/archive

  • Bookmarks a deep link

  • Refreshes the page on a non-root route

…the browser sends a request to the server for that exact path. Without configuration, the server looks for a file at /games/crossword/archive and returns 404, because that path exists only in the SPA router, not on disk.


The Solution ​

All requests for SPA routes must be served the same entry file (index.html or script.js). The SPA then reads the URL and renders the correct view. This is achieved by:

  1. URL rewrite — internally map all SPA paths to the entry file

  2. Reverse proxy — forward requests to a backend that serves the SPA

  3. Fallback / catch-all — treat any non-asset path as an SPA route


Apache ​

Using .htaccess (mod_rewrite) ​

Place this in the document root or in the SPA subdirectory (e.g. /games/.htaccess):

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

  # Don't rewrite files or directories that exist
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d

  # Rewrite everything else to index.html
  RewriteRule ^ index.html [L]
</IfModule>

Notes:

  • Replace /games/ with your SPA base path. Use / if the SPA is at the root.

  • Ensure mod_rewrite is enabled: a2enmod rewrite (Debian/Ubuntu).

  • AllowOverride All must be set for the directory so .htaccess is honoured.

Using VirtualHost (no .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 in a subdirectory (e.g. /games) ​

If the SPA lives under /games and your entry is index.html in that folder:

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 ​

Basic SPA fallback ​

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

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

SPA in a subdirectory (e.g. /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;
  }
}

With reverse proxy to a static/CDN origin ​

If the SPA is served from a CDN or another origin:

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;

    # SPA fallback: if upstream returns 404, serve index
    proxy_intercept_errors on;
    error_page 404 = /index.html;
  }
}

More robust subpath + proxy ​

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;

    # Don't rewrite static assets
    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 (No Apache/Nginx) ​

If you cannot use Apache or Nginx (e.g. serverless, PaaS, or a simple Node server), use one of these approaches.

1. Node.js (Express) ​

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

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

// Serve static files (JS, CSS, images)
app.use(express.static(path.join(__dirname, 'dist')))

// SPA fallback: all other routes serve 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 in subdirectory (e.g. /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" }
  ]
}

For a subpath:

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

3. Netlify (_redirects or netlify.toml) ​

_redirects (in public/ or project root):

txt
/*    /index.html   200

netlify.toml:

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

For subpath /games:

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

4. AWS S3 + CloudFront (Static hosting) ​

S3 does not support rewrites. Use CloudFront Functions or Lambda@Edge:

CloudFront Function (viewer request or origin request):

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

  // Don't rewrite if it looks like a file
  if (uri.includes('.') && !uri.endsWith('.html')) {
    return request
  }

  // SPA fallback
  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 does not support server-side rewrites. Options:

  • Use a 404.html trick: create a custom 404 page that loads your SPA and redirects client-side. Not ideal for SEO or direct links.

  • Use a client-side hash router (#/games/crossword) instead of path-based routing — no server config needed, but URLs are less friendly.

  • Host the SPA elsewhere (Vercel, Netlify, etc.) and point your domain there.

Client integration documentation maintained in-repo.