[object Object]

← back to Dw Domain Fleet

feat: clean fleet /product/ slugs — productSlug() strips 'wallpaper'; handle kept verbatim for Shopify canonical+sample; dual-key route resolves old+new URLs

46abc9e9fc73ded248abbfd8fae9a0a5c32014ae · 2026-05-19 11:22:25 -0700 · SteveStudio2

Files touched

Diff

commit 46abc9e9fc73ded248abbfd8fae9a0a5c32014ae
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Tue May 19 11:22:25 2026 -0700

    feat: clean fleet /product/ slugs — productSlug() strips 'wallpaper'; handle kept verbatim for Shopify canonical+sample; dual-key route resolves old+new URLs
---
 server.js        | 10 +++++++---
 shared/render.js | 24 ++++++++++++++++++------
 2 files changed, 25 insertions(+), 9 deletions(-)

diff --git a/server.js b/server.js
index caf7ff4..23c9a0a 100644
--- a/server.js
+++ b/server.js
@@ -92,10 +92,14 @@ app.get('/catalog', (req, res) => {
 });
 
 app.get('/product/:handle', (req, res) => {
-  const handle = String(req.params.handle || '').slice(0, 200);
-  const p = POOL.find(x => x.handle === handle);
+  const key = String(req.params.handle || '').slice(0, 200);
+  // Dual-key resolve: the real Shopify handle takes priority, then the fleet's
+  // own "wallpaper"-free slug. Serving both keeps any old handle-based URL
+  // working while new links use the clean slug.
+  const p = POOL.find(x => x.handle === key)
+         || POOL.find(x => render.productSlug(x) === key);
   if (!p) return res.status(404).type('html').send(render.aboutPage(cfg));
-  const related = sortProducts(POOL.filter(x => x.handle !== handle), 'newest').slice(0, 10);
+  const related = sortProducts(POOL.filter(x => x !== p), 'newest').slice(0, 10);
   res.type('html').send(render.productPage(cfg, p, related));
 });
 
diff --git a/shared/render.js b/shared/render.js
index 8397487..c22328c 100644
--- a/shared/render.js
+++ b/shared/render.js
@@ -12,6 +12,17 @@ function clean(s) {
   // strip the banned word "Wallpaper" → "Wallcovering" in any rendered copy
   return String(s || '').replace(/wallpapers/gi, 'Wallcoverings').replace(/wallpaper/gi, 'Wallcovering');
 }
+/* productSlug — the fleet's OWN /product/ route key.
+ * Derived from the Shopify handle with "wallpaper" → "wallcovering" so a fleet
+ * URL never carries the off-brand word. The real `handle` is left UNTOUCHED on
+ * the product object — it stays the join key for the rel=canonical tag and the
+ * buy-sample CTA that point at the live designerwallcoverings.com store. Pure
+ * and deterministic: the route can recompute it without storing anything.
+ */
+function productSlug(p) {
+  const h = (p && p.handle) || '';
+  return h.replace(/wallpapers/gi, 'wallcoverings').replace(/wallpaper/gi, 'wallcovering');
+}
 
 const DW = 'https://designerwallcoverings.com';
 
@@ -227,14 +238,15 @@ function jsonld(cfg) {
 /* ---------- card ----------
  * Wrapper is a <div> (NOT <a>) — an <a> wrapping the inner controls would be
  * split by the browser's Adoption Agency Algorithm into sibling grid items.
- * The crawlable link is an inner <a class=card-link> pointing at /product/:handle
- * so search engines can reach every per-SKU page. data-* still drives the
- * quick-view modal.
+ * The crawlable link is an inner <a class=card-link> pointing at
+ * /product/:slug (productSlug — "wallpaper"-free) so search engines reach
+ * every per-SKU page on a clean URL. data-handle still carries the real
+ * Shopify handle for the quick-view modal's buy-sample link.
  */
 function card(p) {
   const hx = dominantHex(p);
   const t = clean(p.title).split('|')[0].trim() || (p.sku||p.handle||'').replace(/-Sample$/i,'') || 'Designer Wallcovering';
-  const href = '/product/' + encodeURIComponent(p.handle);
+  const href = '/product/' + encodeURIComponent(productSlug(p));
   return `<div class="card" data-handle="${esc(p.handle)}"
     data-title="${esc(t)}" data-sku="${esc(p.sku||p.handle)}"
     data-img="${esc(p.image_url)}" data-tags="${esc((p.tags||[]).slice(0,12).join(', '))}"
@@ -513,7 +525,7 @@ function productPage(cfg, p, related) {
     name, sku, image: p.image_url, brand:{ '@type':'Brand', name:'Designer Wallcoverings' },
     url: canonical
   };
-  return head(cfg, title, desc, '/product/' + encodeURIComponent(p.handle), canonical)
+  return head(cfg, title, desc, '/product/' + encodeURIComponent(productSlug(p)), canonical)
     + `<script type="application/ld+json">${JSON.stringify(productLd).replace(/<\/script/gi,'<\\/script')}</script>`
     + jsonld(cfg) + chrome(cfg) + `
 <div style="height:84px"></div>
@@ -553,4 +565,4 @@ ${script(cfg)}
 </body></html>`;
 }
 
-module.exports = { homePage, catalogPage, aboutPage, infoPage, productPage, clean, esc };
+module.exports = { homePage, catalogPage, aboutPage, infoPage, productPage, clean, esc, productSlug };

← 5004030 fix: restore lost pattern name on 4 William Morris products  ·  back to Dw Domain Fleet  ·  chore: broaden .gitignore — snapshot/editor cruft (*.bak, *. febdfbc →