← 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
M server.jsM shared/render.js
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 →