diff --git a/404.html b/404.html
index 9d308e2..5362107 100644
--- a/404.html
+++ b/404.html
@@ -4,6 +4,10 @@
404
+
diff --git a/scripts/redirects.js b/scripts/redirects.js
new file mode 100644
index 0000000..ca0b29c
--- /dev/null
+++ b/scripts/redirects.js
@@ -0,0 +1,83 @@
+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) - 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') {
+ try {
+ const response = await fetch(path);
+ const redirects = await response.json();
+ if (redirects.data) {
+ return activateRedirects(redirects.data);
+ }
+ return [];
+ } catch (error) {
+ return [];
+ }
+}
+
+export async function getRedirect(redirects, path, currentURL) {
+ 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 targetURL = new URL(target, currentURL);
+ // Copy all URL parameters from currentURL to targetURL
+ currentURL.searchParams.forEach((value, key) => {
+ targetURL.searchParams.set(key, value);
+ });
+
+ targetURL.searchParams.set('redirect_from', path);
+ return targetURL.toString();
+ }
+ return null;
+}
+
+export async function isValidRedirect(url) {
+ // we try to fetch the URL, if it fails we return false
+ try {
+ const response = await fetch(url);
+ return response.ok && response.status === 200;
+ } catch (error) {
+ return false;
+ }
+}
+
+export async function applyRedirects(
+ redirects = fetchRedirects(),
+ path = window.location.pathname,
+) {
+ const redirect = await getRedirect(redirects, path, new URL(window.location.href));
+ if (redirect && await isValidRedirect(redirect)) {
+ window.location.replace(redirect);
+ }
+ return path;
+}