diff --git a/404.html b/404.html
index 1591ce10..f1541325 100644
--- a/404.html
+++ b/404.html
@@ -28,6 +28,8 @@
diff --git a/scripts/redirects.js b/scripts/redirects.js
new file mode 100644
index 00000000..165377c0
--- /dev/null
+++ b/scripts/redirects.js
@@ -0,0 +1,62 @@
+function globToRegex(glob) {
+ return new RegExp(`^${glob.replace(/\*/g, '(.*)').replace(/\?/g, '(.)').replace(/\//g, '\\/')}$`);
+}
+
+export function activateRedirects(data) {
+ return data.map((o) => Object.entries(o)
+ .reduce((acc, [k, v]) => {
+ if (k.toLowerCase() === 'from') {
+ acc.from = globToRegex(v);
+ } else if (k.toLowerCase() === 'to') {
+ acc.to = (...replacements) => {
+ replacements.shift();
+ const result = v.replace(/(\$\d+|\*)/g, (matched) => {
+ if (matched.startsWith('$')) {
+ return replacements[matched.slice(1)];
+ }
+ if (matched === '*') {
+ return replacements.shift();
+ }
+ return matched;
+ });
+ return result;
+ };
+ } else if (k.toLowerCase() === 'start') {
+ acc.start = new Date(
+ Date.UTC(1899, 11, 30, 0, 0, 0)
+ + (v - Math.floor(v)) * 86400000 + Math.floor(v) * 86400000,
+ );
+ }
+ return acc;
+ }, {}));
+}
+export async function fetchRedirects(path = '/smart-redirects.json') {
+ const response = await fetch(path);
+ const redirects = await response.json();
+ if (redirects.data) {
+ return activateRedirects(redirects.data);
+ }
+ return [];
+}
+
+export async function applyRedirects(
+ redirects = fetchRedirects(),
+ path = window.location.pathname,
+) {
+ const redirect = (await redirects)
+ .filter((r) => typeof r.start === 'undefined' || r.start.getTime() <= Date.now())
+ .find((r) => r.from.test(path));
+ if (redirect) {
+ const target = redirect.to(path, ...redirect.from.exec(path).slice(1));
+ const currentUrl = new URL(window.location.href);
+ const targetURL = new URL(target, currentUrl.origin);
+ // Copy all URL parameters from currentUrl to targetURL
+ currentUrl.searchParams.forEach((value, key) => {
+ targetURL.searchParams.set(key, value);
+ });
+
+ targetURL.searchParams.set('redirect_from', path);
+ window.location.replace(targetURL.toString());
+ }
+ return path;
+}