← back to Prestige Car Wash
auto-save: 2026-07-26T16:14:26 (1 files) — server.js
1b0a067dbdcc09ea0abbfec9f57912af7495544f · 2026-07-26 16:14:33 -0700 · Steve Abrams
Files touched
Diff
commit 1b0a067dbdcc09ea0abbfec9f57912af7495544f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun Jul 26 16:14:33 2026 -0700
auto-save: 2026-07-26T16:14:26 (1 files) — server.js
---
server.js | 40 +++++++++++++++++++++-------------------
1 file changed, 21 insertions(+), 19 deletions(-)
diff --git a/server.js b/server.js
index dfae8b7..887b37f 100644
--- a/server.js
+++ b/server.js
@@ -444,19 +444,20 @@ function buildJsonLd() {
}
// Open Graph / Twitter tags for the homepage — reuses the page's own <title> and meta
// description so social previews stay in sync with on-page SEO. og:image is a real asset.
-function buildSocialTags(html) {
+function buildSocialTags(html, pagePath) {
// Bail if the HTML already carries og: tags (e.g. added directly to the file) so we
// never emit duplicates that make Facebook's debugger flag the markup. (contrarian gate)
if (/property=["']og:title["']/i.test(html)) return '';
const p = places() || {};
- const url = (p.website || '').replace(/\/$/, '');
+ const base = (p.website || '').replace(/\/$/, '');
+ const url = base ? base + (pagePath || '') : ''; // page-specific canonical + og:url
const title = (html.match(/<title>([^<]*)<\/title>/i) || [])[1] || p.name || 'Prestige Car Wash';
// Attribute-order-independent description extraction, with a real fallback (never a
// blank social card). Handles both name-first and content-first, single or double quotes.
const descMatch = html.match(/<meta\b[^>]*\bname=["']description["'][^>]*\bcontent=["']([^"']*)["']/i)
|| html.match(/<meta\b[^>]*\bcontent=["']([^"']*)["'][^>]*\bname=["']description["']/i);
const desc = (descMatch || [])[1] || `${p.name || 'Prestige Car Wash'} — no-pressure flat pricing, two-bucket hand wash, detailing & ceramic coating in the San Fernando Valley.`;
- const img = url ? url + '/media/svc-full-service.png' : '';
+ const img = base ? base + '/media/svc-full-service.png' : '';
const esc = s => String(s).replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<');
const tags = [
['og:type', 'website'], ['og:site_name', p.name || 'Prestige Car Wash'],
@@ -464,27 +465,28 @@ function buildSocialTags(html) {
['twitter:card', img ? 'summary_large_image' : 'summary'],
['twitter:title', title], ['twitter:description', desc], img && ['twitter:image', img]
].filter(Boolean);
- return tags.map(([k, v]) => k.startsWith('twitter')
+ const metas = tags.map(([k, v]) => k.startsWith('twitter')
? `<meta name="${k}" content="${esc(v)}">`
: `<meta property="${k}" content="${esc(v)}">`).join('\n');
+ return (url ? `<link rel="canonical" href="${esc(url)}">\n` : '') + metas;
}
-function homepageHtml() {
- // Built fresh per request (sync file read + one string replace = microseconds) so the
- // JSON-LD NEVER drifts from live places.json — a stale NAP is the exact thing Google
- // penalizes, which would defeat the point of injecting it. (contrarian gate, 2026-07-26)
- let html = fs.readFileSync(path.join(__dirname, 'public', 'index.html'), 'utf8');
- let inject = buildSocialTags(html);
- const ld = buildJsonLd();
- if (ld) {
- // escape "<" so the JSON can't break out of the <script> tag
- inject += `\n<script type="application/ld+json">${JSON.stringify(ld).replace(/</g, '\\u003c')}</script>`;
- }
+// Built fresh per request (sync file read + one string replace = microseconds) so the
+// injected JSON-LD/OG NEVER drift from live places.json — a stale NAP is the exact thing
+// Google penalizes, which would defeat the point of injecting it. (contrarian gate, 2026-07-26)
+function pageHtml(file, pagePath, ld) {
+ const html = fs.readFileSync(path.join(__dirname, 'public', file), 'utf8');
+ let inject = buildSocialTags(html, pagePath);
+ // escape "<" so the JSON can't break out of the <script> tag
+ if (ld) inject += `\n<script type="application/ld+json">${JSON.stringify(ld).replace(/</g, '\\u003c')}</script>`;
return html.replace('</head>', inject + '\n</head>');
}
-app.get('/', (req, res) => {
- try { res.type('html').send(homepageHtml()); }
- catch { res.sendFile(path.join(__dirname, 'public', 'index.html')); }
-});
+const sendPage = (res, file, pagePath, ld) => {
+ try { res.type('html').send(pageHtml(file, pagePath, ld)); }
+ catch { res.sendFile(path.join(__dirname, 'public', file)); }
+};
+app.get('/', (req, res) => sendPage(res, 'index.html', '', buildJsonLd()));
+app.get('/services', (req, res) => sendPage(res, 'services.html', '/services', null));
+app.get('/contact', (req, res) => sendPage(res, 'contact.html', '/contact', null));
// XML sitemap generated from the public URL — kept in one place, no static file to drift.
app.get('/sitemap.xml', (req, res) => {
← f74fd9f Admin: flag unworked leads going cold (>48h) on cards + funn
·
back to Prestige Car Wash
·
SEO: broaden OG/Twitter + per-page canonical to /services + aa47ccf →