Skip to content

Issue 14989 - Redirect any "v.XX" not including docs to "latest" using a netlify edge function #16378

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ go/**

# archived site version
archived_version

# Local Netlify folder
.netlify
4 changes: 4 additions & 0 deletions netlify.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@
[headers.values]
X-Frame-Options = "SAMEORIGIN"
X-XSS-Protection = "1; mode=block"

[[edge_functions]]
path = "/v*"
function = "redirect-to-latest"
41 changes: 41 additions & 0 deletions netlify/edge-functions/redirect-to-latest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
export default async (request: Request, context) => {
const url = new URL(request.url);
let pathname = url.pathname;
// Normalize pathname by removing consecutive slashes
pathname = pathname.replace(/\/\/+/g, '/');
// Match version paths like /vX.XX/
const versionMatch = pathname.match(/^\/v\d+\.\d+/);

if (versionMatch) {
// Define the main section paths that should be redirected
const redirectableSections = ['/', '/about', '/blog', '/get-involved', '/news', '/search'];
const versionPath = versionMatch[0]; // ex /v1.15
const remainder = pathname.slice(versionPath.length) || '/';
// Check if the remainder starts with an optional leading slash followed by a language code
const languageCodeMatch = remainder.match(/^\/?([a-z]{2})(\/|$)/);
let languageCode: string | null = null;
let remainingPathAfterLang = remainder;

if (languageCodeMatch) {
languageCode = languageCodeMatch[1];
remainingPathAfterLang = remainder.slice(languageCodeMatch[0].length);
}
// If there's a language code and no further path, redirect to the homepage with the language code
if (languageCode && remainingPathAfterLang === '') {
return Response.redirect(new URL(`/latest/${languageCode}/`, url.origin), 301);
}
// Check if the remaining path (after version and optional language code) starts with a redirectable section
const shouldRedirect = redirectableSections.some(section =>
remainingPathAfterLang === section || remainingPathAfterLang.startsWith(`${section}/`) || (section === '/' && remainingPathAfterLang === ''));

if (shouldRedirect) {
const cleanRemainderPath = remainingPathAfterLang.startsWith('/') ? remainingPathAfterLang.substring(1) : remainingPathAfterLang;
const newPath = `/latest/${languageCode ? `${languageCode}/` : ''}${cleanRemainderPath}`;
return Response.redirect(new URL(newPath, url.origin), 301);
}
// For version-specific paths we want to keep, tell Netlify to stop processing this function
return context.next();
}
// For all non-version paths, pass through
return context.next();
};