← back to Thesetdecorator
TSD SEO fix: dedupe canonical in renderWithSEO (DTD verdict B)
6c2469ceccc056fccbf2dd7492c91ff154eecb7e · 2026-06-03 09:19:56 -0700 · Steve
Strip any static <link rel=canonical> from the .html template before injecting
the clean-URL one. 16 of 17 PUBLIC_PAGES carried a static .html-form canonical;
their clean-URL routes (/best-sellers etc.) were serving TWO conflicting
canonical tags (/slug vs /slug.html). Now exactly one, in the preferred
no-.html form. Verified on free port: all 17 clean routes -> 1 canonical;
static .html paths unaffected (still 1). Self-heals for future PUBLIC_PAGES.
Local only; needs deploy to affect prod (gated).
Files touched
Diff
commit 6c2469ceccc056fccbf2dd7492c91ff154eecb7e
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Jun 3 09:19:56 2026 -0700
TSD SEO fix: dedupe canonical in renderWithSEO (DTD verdict B)
Strip any static <link rel=canonical> from the .html template before injecting
the clean-URL one. 16 of 17 PUBLIC_PAGES carried a static .html-form canonical;
their clean-URL routes (/best-sellers etc.) were serving TWO conflicting
canonical tags (/slug vs /slug.html). Now exactly one, in the preferred
no-.html form. Verified on free port: all 17 clean routes -> 1 canonical;
static .html paths unaffected (still 1). Self-heals for future PUBLIC_PAGES.
Local only; needs deploy to affect prod (gated).
---
server.js | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/server.js b/server.js
index c11f7da..2715d20 100644
--- a/server.js
+++ b/server.js
@@ -299,6 +299,16 @@ function renderWithSEO(slug, req) {
} else {
injected = `\n<link rel="canonical" href="${escAttr(pageUrl)}">\n`;
}
+ // Strip any static <link rel="canonical"> already in the .html template before
+ // injecting ours. Several PUBLIC_PAGES carry a static canonical in the .html
+ // form (e.g. /best-sellers.html); without this strip the clean-URL route would
+ // emit TWO canonical tags pointing at conflicting URLs (/slug vs /slug.html).
+ // This makes renderWithSEO the single source of truth — exactly one canonical,
+ // in the preferred no-.html form — and self-heals for any future page added to
+ // PUBLIC_PAGES. Only canonical <link> tags are removed; JSON-LD/other head
+ // content is untouched. (The .html files served directly via express.static
+ // keep their single static canonical and are unaffected.)
+ html = html.replace(/[ \t]*<link\b[^>]*\brel=["']?canonical["']?[^>]*>[ \t]*\n?/gi, '');
const rendered = html.replace('</head>', injected + '</head>');
SEO_CACHE.set(cacheKey, { ts: Date.now(), html: rendered });
return rendered;
← 1ec82e5 backlog: mark loop #3 done (heading-order) + record audited-
·
back to Thesetdecorator
·
backlog: loop #4 (canonical-dedup fix) + queue deploy for St 619da85 →