Skip to content

Instantly share code, notes, and snippets.

@supersonictw
Last active September 23, 2025 03:18
Show Gist options
  • Select an option

  • Save supersonictw/34129b3e041286947cb89b014e53a43a to your computer and use it in GitHub Desktop.

Select an option

Save supersonictw/34129b3e041286947cb89b014e53a43a to your computer and use it in GitHub Desktop.
Generating SPA routes for NGINX to respond status code 404 correctly.
// generate-spa-nginx-routes.js
// SPDX-License-Identifier: MIT (https://ncurl.xyz/s/Kkn2DQsNR)
// nginx.conf example:
//
// include routes_params;
// ...
// location / {
// try_files $uri $uri/ $routes =404;
// }
// ...
//
import {writeFileSync} from 'node:fs';
import {routes} from './src/router/index.js';
const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\\]/g, '\\$&');
const generatedPaths = new Set();
const generateNginxRules = (routes, basePath = '', content = '') => routes.map((route) => {
if (route.path.includes(':pathMatch')) {
return '';
}
const fullPath = route.path.startsWith('/') ?
route.path :
(basePath + '/' + route.path).replace(/\/+/g, '/');
if (!generatedPaths.has(fullPath) && !route.redirect) {
if (fullPath.includes(':')) {
const pattern = `^${fullPath.replace(/:[^/]+/g, '[^/]+').replace(/\/?$/, '')}(/)?$`;
content += `\t~${pattern} /index.html;\n`;
} else {
const pattern = `^${escapeRegExp(fullPath.replace(/\/?$/, ''))}(/)?$`;
content += `\t~${pattern} /index.html;\n`;
}
generatedPaths.add(fullPath);
}
if (route.children) {
return generateNginxRules(route.children, fullPath, content);
}
return content;
}).join('');
const mapContent = `# Auto-generated by generate-spa-nginx-routes.js
map $uri $render {
\tdefault "";
${generateNginxRules(routes)}
}`;
const routesFilePath = new URL(
'routes_params',
import.meta.url,
);
writeFileSync(routesFilePath, mapContent);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment