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.
This commit is contained in:
@@ -243,7 +243,9 @@ function updateJsonLd(schema) {
|
|||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
const meta = to.meta || {};
|
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 title = meta.title || 'SingleChat';
|
||||||
const description = meta.description || '';
|
const description = meta.description || '';
|
||||||
const keywords = meta.keywords || '';
|
const keywords = meta.keywords || '';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Server as SocketIOServer } from 'socket.io';
|
|||||||
import cookieParser from 'cookie-parser';
|
import cookieParser from 'cookie-parser';
|
||||||
import session from 'express-session';
|
import session from 'express-session';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath, URL as NodeURL } from 'url';
|
||||||
import { dirname, join } from 'path';
|
import { dirname, join } from 'path';
|
||||||
import { setupBroadcast } from './broadcast.js';
|
import { setupBroadcast } from './broadcast.js';
|
||||||
import { setupRoutes } from './routes.js';
|
import { setupRoutes } from './routes.js';
|
||||||
@@ -117,6 +117,44 @@ if (IS_PRODUCTION) {
|
|||||||
|
|
||||||
next();
|
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
|
// Statische Dateien aus docroot
|
||||||
@@ -140,10 +178,9 @@ if (IS_PRODUCTION) {
|
|||||||
app.use(express.static(distPath));
|
app.use(express.static(distPath));
|
||||||
// Fallback für Vue Router (SPA) - muss am Ende stehen
|
// Fallback für Vue Router (SPA) - muss am Ende stehen
|
||||||
app.get('*', (req, res) => {
|
app.get('*', (req, res) => {
|
||||||
// Überspringe SEO-Routes in Production (werden bereits von setupSEORoutes behandelt)
|
// Hinweis: SEO-Routen (/, /partners, …) werden bereits von setupSEORoutes registriert
|
||||||
if (IS_PRODUCTION && (req.path === '/' || req.path === '/partners' || req.path === '/feedback' || req.path === '/faq' || req.path === '/regeln' || req.path === '/sicherheit')) {
|
// und greifen vor diesem Catch-All. Kein frühes return ohne res.* hier.
|
||||||
return; // Route wurde bereits behandelt
|
|
||||||
}
|
|
||||||
// In Production: /src/ Pfade sollten nicht existieren (404)
|
// In Production: /src/ Pfade sollten nicht existieren (404)
|
||||||
if (IS_PRODUCTION && req.path.startsWith('/src/')) {
|
if (IS_PRODUCTION && req.path.startsWith('/src/')) {
|
||||||
res.status(404).send('Not found');
|
res.status(404).send('Not found');
|
||||||
|
|||||||
@@ -283,11 +283,14 @@ export function setupSEORoutes(app, __dirname) {
|
|||||||
app.get(route, (req, res) => {
|
app.get(route, (req, res) => {
|
||||||
const html = generateHTML(route, meta, __dirname);
|
const html = generateHTML(route, meta, __dirname);
|
||||||
if (html) {
|
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);
|
res.send(html);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existsSync(distIndexPath)) {
|
if (existsSync(distIndexPath)) {
|
||||||
|
res.set('Link', `<${meta.ogUrl}>; rel="canonical"`);
|
||||||
res.sendFile(distIndexPath);
|
res.sendFile(distIndexPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user