You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Bing Webmaster Tools / SEO audit flagged the following errors and notices:
Error: More than one <title> tag (2 instances found)
Error: More than one Meta Description tag (<meta name="description">, 2 instances found)
Notice: More than one canonical tag (<link rel="canonical">, 2 instances found)
Root Cause Analysis
Static vs Client-Side Hydration (Helmet):
Static HTML files (index.html and SPA route stubs generated by scripts/generate-spa-stubs.js) output static <title>, <meta name="description">, and <link rel="canonical"> elements.
When React mounts on the client side, react-helmet-async (src/components/SEO.tsx) injects its own <meta> and <link> tags into the <head>.
Without data-rh="true" attributes on prerendered tags, Helmet cannot associate existing head tags with its managed state, causing it to append duplicate DOM nodes when JS executes during crawler rendering.
Critical Requirements & Constraints
Important
Do NOT undo per-route unique metadata:
In previous iterations, generic Vite SPA fallback workarounds caused all child routes to get overwritten with the homepage/root metadata.
Every individual route (/about, /research/*, /devai/, /demos/*, etc.) must retain its unique seoTitle, seoDescription, canonical URL, and structured JSON-LD generated from its markdown frontmatter and route discovery.
Tag deduplication must be achieved by aligning Helmet hydration (adding data-rh="true" attributes on prerendered static tags), NOT by removing per-route static prerendering or falling back to a single generic template.
Action Items
Tag Deduplication via Helmet Alignment:
Add data-rh="true" attributes to all prerendered static <title>, <meta name="description">, <link rel="canonical">, and OG/Twitter tags in index.html and scripts/generate-spa-stubs.js.
Preserve Per-Route Unique Metadata:
Verify that each static HTML stub preserves unique titles, descriptions, canonical URLs, and structured JSON-LD per route.
Automated Head Tag Tests:
Add unit and E2E/Playwright test assertions to verify that:
Exactly 1 <title>, 1 <meta name="description">, and 1 <link rel="canonical"> exist in the DOM post-hydration.
Route navigation properly updates metadata and maintains unique per-route values.
Problem
Bing Webmaster Tools / SEO audit flagged the following errors and notices:
<title>tag (2 instances found)<meta name="description">, 2 instances found)<link rel="canonical">, 2 instances found)Root Cause Analysis
index.htmland SPA route stubs generated byscripts/generate-spa-stubs.js) output static<title>,<meta name="description">, and<link rel="canonical">elements.react-helmet-async(src/components/SEO.tsx) injects its own<meta>and<link>tags into the<head>.data-rh="true"attributes on prerendered tags, Helmet cannot associate existing head tags with its managed state, causing it to append duplicate DOM nodes when JS executes during crawler rendering.Critical Requirements & Constraints
Important
Do NOT undo per-route unique metadata:
/about,/research/*,/devai/,/demos/*, etc.) must retain its uniqueseoTitle,seoDescription, canonical URL, and structured JSON-LD generated from its markdown frontmatter and route discovery.data-rh="true"attributes on prerendered static tags), NOT by removing per-route static prerendering or falling back to a single generic template.Action Items
data-rh="true"attributes to all prerendered static<title>,<meta name="description">,<link rel="canonical">, and OG/Twitter tags inindex.htmlandscripts/generate-spa-stubs.js.<title>, 1<meta name="description">, and 1<link rel="canonical">exist in the DOM post-hydration.