diff --git a/frontend/public/sitemap.xml b/frontend/public/sitemap.xml
index 76009aae..a04dc784 100644
--- a/frontend/public/sitemap.xml
+++ b/frontend/public/sitemap.xml
@@ -87,16 +87,6 @@
monthly
0.85
-
- https://velxio.dev/v2/
- monthly
- 0.9
-
-
- https://velxio.dev/v2-5/
- monthly
- 0.9
-
https://velxio.dev/v3/
weekly
diff --git a/frontend/scripts/prerender-seo.mjs b/frontend/scripts/prerender-seo.mjs
index 6d199084..d510c295 100644
--- a/frontend/scripts/prerender-seo.mjs
+++ b/frontend/scripts/prerender-seo.mjs
@@ -147,7 +147,11 @@ try {
} else {
html = html.replace('', ` ${canonicalTag}\n `);
}
- html = withHreflang(html, route.path);
+ // hreflang only for a page that is its own canonical; a page pointing
+ // its canonical elsewhere (/v2 -> /) must not declare alternates.
+ if (withSlash(seoMeta.url) === withSlash(`${DOMAIN}${route.path}`)) {
+ html = withHreflang(html, route.path);
+ }
// Replace OG tags
html = html.replace(
diff --git a/frontend/src/seoRoutes.ts b/frontend/src/seoRoutes.ts
index 5a2dca66..60855142 100644
--- a/frontend/src/seoRoutes.ts
+++ b/frontend/src/seoRoutes.ts
@@ -236,24 +236,31 @@ export const SEO_ROUTES: SeoRoute[] = [
{
path: '/v2',
priority: 0.9,
+ // Superseded release page. Canonical points at the home (seoMeta.url)
+ // and noindex keeps it out of the sitemap: /v2 and /v2-5 were taking
+ // two brand sitelink slots (5.9k impressions, 29 clicks) that the
+ // editor and the simulator landings should hold. The page still
+ // renders for old links.
+ noindex: true,
changefreq: 'monthly',
seoMeta: {
title: 'Velxio 2.0 — Multi-Board Embedded Simulator | ESP32, Raspberry Pi, Arduino, RISC-V',
description:
'Velxio 2.0 is here. Simulate Arduino, ESP32, Raspberry Pi Pico, and Raspberry Pi 3 in your browser. 19 boards, 68+ examples, realistic sensor simulation. Free and open-source.',
- url: `${DOMAIN}/v2`,
+ url: `${DOMAIN}/`,
},
},
{
path: '/v2-5',
priority: 0.9,
+ noindex: true, // see /v2
changefreq: 'monthly',
seoMeta: {
title:
'Velxio 2.5 — Arduino + SPICE Analog Circuit Simulator in Your Browser | ngspice-WASM',
description:
'Velxio 2.5 brings real-time analog circuit simulation via ngspice-WASM. Hybrid digital + analog co-simulation: resistors, capacitors, inductors, op-amps, transistors, voltmeters, ammeters — wired to Arduino, ESP32, RP2040 GPIO/ADC. 40+ circuit examples. Free and open-source.',
- url: `${DOMAIN}/v2-5`,
+ url: `${DOMAIN}/`,
},
},
{
diff --git a/frontend/src/utils/useSEO.ts b/frontend/src/utils/useSEO.ts
index f14178b3..2e7a0a19 100644
--- a/frontend/src/utils/useSEO.ts
+++ b/frontend/src/utils/useSEO.ts
@@ -70,6 +70,13 @@ function localeLinks(url: string): { canonical: string; alternates: { hreflang:
return { canonical: canonicalDefault, alternates: [] };
}
if (!isLocalizedRoute(strippedPath)) return { canonical: canonicalDefault, alternates: [] };
+ // A page whose canonical is another page (/v2 -> /) keeps that canonical
+ // as given and declares no alternates.
+ if (typeof window !== 'undefined') {
+ const herePath = stripLocaleFromPath(window.location.pathname).replace(/\/+$/, '') || '/';
+ const urlPath = strippedPath.replace(/\/+$/, '') || '/';
+ if (herePath !== urlPath) return { canonical: canonicalDefault, alternates: [] };
+ }
const current = typeof window !== 'undefined' ? getLocaleFromPath(window.location.pathname) : DEFAULT_LOCALE;
const variant = (l: (typeof LOCALES)[number]) => withTrailingSlash(`${DOMAIN}${localizedPath(strippedPath, l)}`);
return {