feat(SEO, Sitemap, Routing): enhance SEO and sitemap for new features
- Updated update-sitemap.sh to include new URLs for Vereinssoftware, Mitgliederverwaltung, Trainingsplanung, and Turniersoftware with appropriate lastmod dates and change frequencies. - Enhanced server.js and seo.js with SEO configurations for the new pages, ensuring proper indexing and descriptions. - Added new routes in router.js for the additional features, improving navigation and user access. - Updated Home.vue to include links to the new features, enhancing user engagement and visibility.
This commit is contained in:
@@ -62,6 +62,10 @@ import HttpError from './exceptions/HttpError.js';
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3005;
|
||||
const PUBLIC_SITE_URL = process.env.PUBLIC_SITE_URL || 'https://tt-tagebuch.de';
|
||||
const publicSiteOrigin = new URL(PUBLIC_SITE_URL).origin;
|
||||
const publicSiteHost = new URL(PUBLIC_SITE_URL).host;
|
||||
const publicSiteProtocol = new URL(PUBLIC_SITE_URL).protocol.replace(':', '');
|
||||
|
||||
function captureRawBody(req, res, buf, encoding) {
|
||||
if (!buf || buf.length === 0) return;
|
||||
@@ -83,6 +87,26 @@ const SEO_ROUTE_CONFIG = {
|
||||
description: SEO_DEFAULTS.description,
|
||||
robots: 'index,follow',
|
||||
},
|
||||
'/vereinssoftware-tischtennis': {
|
||||
title: 'Vereinssoftware für Tischtennisvereine | Trainingstagebuch',
|
||||
description: 'Webbasierte Vereinssoftware für Tischtennisvereine mit Mitgliederverwaltung, Trainingsplanung, Mannschaftsorganisation, Turnieren und Auswertungen.',
|
||||
robots: 'index,follow',
|
||||
},
|
||||
'/mitgliederverwaltung-verein': {
|
||||
title: 'Mitgliederverwaltung für Vereine | Trainingstagebuch',
|
||||
description: 'Mitgliederverwaltung für Vereine mit Stammdaten, Rollen, Gruppenbezug und organisatorischer Verbindung zu Training und Vereinsabläufen.',
|
||||
robots: 'index,follow',
|
||||
},
|
||||
'/trainingsplanung-tischtennis': {
|
||||
title: 'Trainingsplanung für Tischtennisvereine | Trainingstagebuch',
|
||||
description: 'Trainingsplanung für Tischtennisvereine mit Gruppen, Anwesenheiten, Trainingstagebuch und digitaler Organisation von Trainingstagen.',
|
||||
robots: 'index,follow',
|
||||
},
|
||||
'/turniersoftware-tischtennis': {
|
||||
title: 'Turniersoftware für Tischtennis | Trainingstagebuch',
|
||||
description: 'Turniersoftware für Tischtennisvereine mit Teilnehmerverwaltung, Gruppen, Paarungen, Ergebnissen und Organisation interner oder offizieller Turniere.',
|
||||
robots: 'index,follow',
|
||||
},
|
||||
'/login': {
|
||||
title: 'Login | Trainingstagebuch',
|
||||
description: 'Im Trainingstagebuch einloggen und Vereinsdaten, Trainingsplanung, Mitglieder und Turniere verwalten.',
|
||||
@@ -175,6 +199,41 @@ function escapeHtmlAttribute(value = '') {
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
|
||||
function isPublicFacingHost(host = '') {
|
||||
const normalizedHost = String(host).toLowerCase().replace(/:\d+$/, '');
|
||||
return normalizedHost === 'tt-tagebuch.de' || normalizedHost === 'www.tt-tagebuch.de';
|
||||
}
|
||||
|
||||
function getCanonicalRequestContext(req) {
|
||||
const forwardedProtoHeader = req.get('x-forwarded-proto');
|
||||
const forwardedHostHeader = req.get('x-forwarded-host');
|
||||
const forwardedProto = forwardedProtoHeader?.split(',')[0]?.trim();
|
||||
const forwardedHost = forwardedHostHeader?.split(',')[0]?.trim();
|
||||
const requestHost = forwardedHost || req.get('host') || publicSiteHost;
|
||||
const requestProtocol = forwardedProto || req.protocol || publicSiteProtocol;
|
||||
const canonicalHost = isPublicFacingHost(requestHost) ? requestHost.replace(/^www\./, '') : publicSiteHost;
|
||||
const canonicalProtocol = isPublicFacingHost(requestHost) ? requestProtocol : publicSiteProtocol;
|
||||
|
||||
return {
|
||||
requestHost,
|
||||
requestProtocol,
|
||||
canonicalHost,
|
||||
canonicalProtocol,
|
||||
canonicalOrigin: `${canonicalProtocol}://${canonicalHost}`,
|
||||
};
|
||||
}
|
||||
|
||||
function stripJsonLdByType(html, schemaType) {
|
||||
const escapedType = schemaType.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const pattern = new RegExp(
|
||||
`<script type="application/ld\\+json">[\\s\\S]*?"@type"\\s*:\\s*"${escapedType}"[\\s\\S]*?<\\/script>\\s*`,
|
||||
'g'
|
||||
);
|
||||
return html.replace(pattern, '');
|
||||
}
|
||||
|
||||
app.set('trust proxy', true);
|
||||
|
||||
// CORS-Konfiguration - Socket.IO hat seine eigene CORS-Konfiguration
|
||||
app.use(cors({
|
||||
origin: true,
|
||||
@@ -244,6 +303,14 @@ app.use('/api/member-orders', memberOrderRoutes);
|
||||
|
||||
// Middleware für dynamischen kanonischen Tag (vor express.static)
|
||||
const setCanonicalTag = (req, res, next) => {
|
||||
const normalizedPath = normalizeSeoPath(req.path);
|
||||
const { requestHost, requestProtocol, canonicalHost, canonicalProtocol, canonicalOrigin } = getCanonicalRequestContext(req);
|
||||
|
||||
if (requestHost.replace(/^www\./, '') === canonicalHost && requestHost !== canonicalHost) {
|
||||
const redirectUrl = `${canonicalProtocol}://${canonicalHost}${normalizedPath === '/' ? '/' : normalizedPath}${req.url.includes('?') ? req.url.slice(req.url.indexOf('?')) : ''}`;
|
||||
return res.redirect(301, redirectUrl);
|
||||
}
|
||||
|
||||
// Socket.IO-Requests komplett ignorieren
|
||||
if (req.path.startsWith('/socket.io/')) {
|
||||
return next();
|
||||
@@ -269,12 +336,7 @@ const setCanonicalTag = (req, res, next) => {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Bestimme die kanonische URL (bevorzuge non-www)
|
||||
const protocol = req.protocol || 'https';
|
||||
const host = req.get('host') || 'tt-tagebuch.de';
|
||||
const canonicalHost = host.replace(/^www\./, ''); // Entferne www falls vorhanden
|
||||
const normalizedPath = normalizeSeoPath(req.path);
|
||||
const canonicalUrl = `${protocol}://${canonicalHost}${normalizedPath === '/' ? '' : normalizedPath}`;
|
||||
const canonicalUrl = `${canonicalOrigin}${normalizedPath === '/' ? '' : normalizedPath}`;
|
||||
const seo = getSeoConfigForPath(normalizedPath);
|
||||
|
||||
let updatedData = data.replace(
|
||||
@@ -322,6 +384,11 @@ const setCanonicalTag = (req, res, next) => {
|
||||
`<meta name="twitter:description" content="${escapeHtmlAttribute(seo.description)}" />`
|
||||
);
|
||||
|
||||
if (normalizedPath !== '/') {
|
||||
updatedData = stripJsonLdByType(updatedData, 'FAQPage');
|
||||
updatedData = stripJsonLdByType(updatedData, 'SoftwareApplication');
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.send(updatedData);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user