diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro
index 948b0c121d..b648c9eb0b 100644
--- a/src/layouts/Layout.astro
+++ b/src/layouts/Layout.astro
@@ -23,10 +23,6 @@ const {
canonicalPathname,
} = Astro.props;
-const pageTitle = title
- ? `${title} · Express.js`
- : 'Express.js · Node.js web application framework';
-
const pathname = Astro.url.pathname.replace(/\/$/, '');
const segments = pathname.split('/').filter(Boolean);
const langSegment = segments[0] || 'en';
@@ -35,6 +31,18 @@ const isApi =
restSegments[0] === 'api' || (restSegments[1] === 'api' && /^\dx$/.test(restSegments[0] ?? ''));
const isBlogOrApi = restSegments[0] === 'blog' || isApi;
+// Versioned pages carry the version in title/description so search engines
+// don't treat 4x and 5x as duplicates.
+const versionSegment = /^\dx$/.test(restSegments[0] ?? '') ? restSegments[0] : undefined;
+const versionLabel = versionSegment && `Express.js ${versionSegment[0]}.x`;
+
+const pageTitle = title
+ ? `${title} · ${versionLabel ?? 'Express.js'}`
+ : 'Express.js · Node.js web application framework';
+const pageDescription = versionLabel
+ ? `${description} Documentation for ${versionLabel}.`
+ : description;
+
// Blog and API content is not translated, so their canonical is the English
// page and they emit no hreflang alternates.
let canonicalPath = canonicalPathname ?? Astro.url.pathname;
@@ -94,7 +102,7 @@ const lang = getLangFromUrl(Astro.url);
-
+
@@ -128,7 +136,7 @@ const lang = getLangFromUrl(Astro.url);
-
+
{authors?.map((author) => )}
@@ -136,7 +144,7 @@ const lang = getLangFromUrl(Astro.url);
-
+