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 {