From 27f928d8a497ceb8bcce9e9df2173df13de9d98a Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Mon, 30 Mar 2026 09:05:12 +0200 Subject: [PATCH] Enhance SEO and URL handling in router and server - Updated the router to ensure canonical URLs are correctly formatted, particularly for the home path. - Added middleware in the server to remove tracking/session query parameters, preventing duplicate URLs from being indexed by Google. - Set canonical link headers in SEO routes to assist search engines in URL attribution. These changes improve SEO performance and ensure cleaner URL structures across the application. --- client/src/router/index.js | 4 +++- server/index.js | 47 ++++++++++++++++++++++++++++++++++---- server/routes-seo.js | 3 +++ 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/client/src/router/index.js b/client/src/router/index.js index cfa4cc2..b800b46 100644 --- a/client/src/router/index.js +++ b/client/src/router/index.js @@ -243,7 +243,9 @@ function updateJsonLd(schema) { router.beforeEach((to, from, next) => { const meta = to.meta || {}; - const pageUrl = `${SITE_URL}${to.path}`; + // Immer eine eindeutige kanonische URL (Home explizit mit / am Ende der Origin) + const path = to.path === '' ? '/' : to.path; + const pageUrl = path === '/' ? `${SITE_URL}/` : `${SITE_URL}${path}`; const title = meta.title || 'SingleChat'; const description = meta.description || ''; const keywords = meta.keywords || ''; diff --git a/server/index.js b/server/index.js index 8870109..5d4170b 100644 --- a/server/index.js +++ b/server/index.js @@ -4,7 +4,7 @@ import { Server as SocketIOServer } from 'socket.io'; import cookieParser from 'cookie-parser'; import session from 'express-session'; import cors from 'cors'; -import { fileURLToPath } from 'url'; +import { fileURLToPath, URL as NodeURL } from 'url'; import { dirname, join } from 'path'; import { setupBroadcast } from './broadcast.js'; import { setupRoutes } from './routes.js'; @@ -117,6 +117,44 @@ if (IS_PRODUCTION) { next(); }); + + // Tracking-/Session-Query entfernen (wtd, js=no), damit Google keine Duplikat-URLs indexiert. + // Suchkonsole: "Alternative Seite mit richtigem kanonischen Tag" fuer /?wtd=... verschwindet nach Neu-Crawl. + app.use((req, res, next) => { + if (req.method !== 'GET' && req.method !== 'HEAD') { + next(); + return; + } + if (req.path.startsWith('/api')) { + next(); + return; + } + const host = req.get('host') || 'localhost'; + const proto = req.get('x-forwarded-proto') || req.protocol || 'https'; + const raw = req.originalUrl.split('#')[0]; + let u; + try { + u = new NodeURL(raw, `${proto}://${host}`); + } catch { + next(); + return; + } + const hadWtd = u.searchParams.has('wtd'); + const hadJsNo = u.searchParams.get('js') === 'no'; + if (!hadWtd && !hadJsNo) { + next(); + return; + } + if (hadWtd) u.searchParams.delete('wtd'); + if (hadJsNo) u.searchParams.delete('js'); + const search = u.searchParams.toString(); + const dest = u.pathname + (search ? `?${search}` : ''); + if (dest !== raw) { + res.redirect(301, dest); + return; + } + next(); + }); } // Statische Dateien aus docroot @@ -140,10 +178,9 @@ if (IS_PRODUCTION) { app.use(express.static(distPath)); // Fallback für Vue Router (SPA) - muss am Ende stehen app.get('*', (req, res) => { - // Überspringe SEO-Routes in Production (werden bereits von setupSEORoutes behandelt) - if (IS_PRODUCTION && (req.path === '/' || req.path === '/partners' || req.path === '/feedback' || req.path === '/faq' || req.path === '/regeln' || req.path === '/sicherheit')) { - return; // Route wurde bereits behandelt - } + // Hinweis: SEO-Routen (/, /partners, …) werden bereits von setupSEORoutes registriert + // und greifen vor diesem Catch-All. Kein frühes return ohne res.* hier. + // In Production: /src/ Pfade sollten nicht existieren (404) if (IS_PRODUCTION && req.path.startsWith('/src/')) { res.status(404).send('Not found'); diff --git a/server/routes-seo.js b/server/routes-seo.js index da00736..9d5cb09 100644 --- a/server/routes-seo.js +++ b/server/routes-seo.js @@ -283,11 +283,14 @@ export function setupSEORoutes(app, __dirname) { app.get(route, (req, res) => { const html = generateHTML(route, meta, __dirname); if (html) { + // Zusätzliches kanonisches Signal (neben link rel= im HTML) – hilft Google bei der Zuordnung. + res.set('Link', `<${meta.ogUrl}>; rel="canonical"`); res.send(html); return; } if (existsSync(distIndexPath)) { + res.set('Link', `<${meta.ogUrl}>; rel="canonical"`); res.sendFile(distIndexPath); return; }