From 9d023b534d6b9fde771fc575c04588cbc880eac3 Mon Sep 17 00:00:00 2001
From: "Torsten Schulz (local)"
Date: Fri, 27 Mar 2026 11:42:11 +0100
Subject: [PATCH] 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.
---
backend/server.js | 79 ++++++-
frontend/public/sitemap.xml | 24 ++
frontend/src/components/SeoLandingPage.vue | 215 ++++++++++++++++++
frontend/src/router.js | 8 +
frontend/src/utils/seo.js | 20 ++
.../src/views/ClubMemberManagementPage.vue | 66 ++++++
frontend/src/views/Home.vue | 52 +++++
.../src/views/TableTennisClubSoftware.vue | 67 ++++++
.../TableTennisTournamentSoftwarePage.vue | 66 ++++++
frontend/src/views/TrainingPlanningPage.vue | 66 ++++++
update-sitemap.sh | 28 +++
11 files changed, 685 insertions(+), 6 deletions(-)
create mode 100644 frontend/src/components/SeoLandingPage.vue
create mode 100644 frontend/src/views/ClubMemberManagementPage.vue
create mode 100644 frontend/src/views/TableTennisClubSoftware.vue
create mode 100644 frontend/src/views/TableTennisTournamentSoftwarePage.vue
create mode 100644 frontend/src/views/TrainingPlanningPage.vue
diff --git a/backend/server.js b/backend/server.js
index 27985cb1..357a4a11 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -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(
+ `
+
+
diff --git a/frontend/src/router.js b/frontend/src/router.js
index 83596da8..9d81abf9 100644
--- a/frontend/src/router.js
+++ b/frontend/src/router.js
@@ -8,6 +8,10 @@ const Activate = () => import('./views/Activate.vue');
const ForgotPassword = () => import('./views/ForgotPassword.vue');
const ResetPassword = () => import('./views/ResetPassword.vue');
const Home = () => import('./views/Home.vue');
+const TableTennisClubSoftware = () => import('./views/TableTennisClubSoftware.vue');
+const ClubMemberManagementPage = () => import('./views/ClubMemberManagementPage.vue');
+const TrainingPlanningPage = () => import('./views/TrainingPlanningPage.vue');
+const TableTennisTournamentSoftwarePage = () => import('./views/TableTennisTournamentSoftwarePage.vue');
const CreateClub = () => import('./views/CreateClub.vue');
const ClubView = () => import('./views/ClubView.vue');
const MembersView = () => import('./views/MembersView.vue');
@@ -38,6 +42,10 @@ const routes = [
{ path: '/forgot-password', name: 'forgot-password', component: ForgotPassword, meta: { public: true } },
{ path: '/reset-password/:token', name: 'reset-password', component: ResetPassword, meta: { public: true } },
{ path: '/', name: 'home', component: Home, meta: { public: true } },
+ { path: '/vereinssoftware-tischtennis', name: 'club-software-seo', component: TableTennisClubSoftware, meta: { public: true } },
+ { path: '/mitgliederverwaltung-verein', name: 'member-management-seo', component: ClubMemberManagementPage, meta: { public: true } },
+ { path: '/trainingsplanung-tischtennis', name: 'training-planning-seo', component: TrainingPlanningPage, meta: { public: true } },
+ { path: '/turniersoftware-tischtennis', name: 'tournament-software-seo', component: TableTennisTournamentSoftwarePage, meta: { public: true } },
{ path: '/createclub', name: 'create-club', component: CreateClub },
{ path: '/showclub/:clubId', name: 'show-club', component: ClubView },
{ path: '/members', name: 'members', component: MembersView },
diff --git a/frontend/src/utils/seo.js b/frontend/src/utils/seo.js
index be790ba1..8e0105b7 100644
--- a/frontend/src/utils/seo.js
+++ b/frontend/src/utils/seo.js
@@ -14,6 +14,26 @@ const ROUTE_SEO = {
description: DEFAULT_SEO.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.',
diff --git a/frontend/src/views/ClubMemberManagementPage.vue b/frontend/src/views/ClubMemberManagementPage.vue
new file mode 100644
index 00000000..ddf48922
--- /dev/null
+++ b/frontend/src/views/ClubMemberManagementPage.vue
@@ -0,0 +1,66 @@
+
+
+
+
+
diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue
index 9458213c..7877a26a 100644
--- a/frontend/src/views/Home.vue
+++ b/frontend/src/views/Home.vue
@@ -85,6 +85,9 @@
Trainingsgruppen, Teilnahmen und Team-Zuordnungen direkt mit den Mitgliedsdaten
verknüpfen.
+
+ Mehr zur Mitgliederverwaltung
+
Trainingsplanung und Trainingstagebuch
@@ -93,6 +96,9 @@
halten Aktivitäten im Trainingstagebuch fest. Das eignet sich besonders für
Tischtennistraining mit wechselnden Gruppen, Anwesenheiten und Stationsformen.
+
+ Mehr zur Trainingsplanung
+
Turniersoftware für Vereinswettbewerbe
@@ -101,6 +107,9 @@
Wettbewerbe. Teilnehmer, Gruppen, Auslosungen, Ergebnisse und Übersichten lassen
sich in einer Anwendung verwalten.
+
+ Mehr zur Turniersoftware
+
Team-Management und Statistiken
@@ -109,9 +118,30 @@
Damit eignet sich Trainingstagebuch nicht nur für die Trainingsorganisation, sondern
auch für die laufende Vereinsarbeit über die ganze Saison.
+
+ Mehr zur Vereinssoftware
+
+
+ Öffentliche Themenseiten
+
+
+ Vereinssoftware für Tischtennisvereine
+
+
+ Mitgliederverwaltung für Vereine
+
+
+ Trainingsplanung für Tischtennisvereine
+
+
+ Turniersoftware für Tischtennis
+
+
+
+
{{ $t('home.whatCanYouDo') }}
@@ -505,12 +535,34 @@ export default {
margin-bottom: 0;
}
+.topic-link,
+.seo-hub-link {
+ color: var(--primary-color);
+ font-weight: 600;
+ text-decoration: none;
+}
+
+.topic-link:hover,
+.seo-hub-link:hover {
+ text-decoration: underline;
+}
+
.seo-intro-points {
display: grid;
gap: 0.85rem;
align-content: start;
}
+.seo-hub {
+ padding: 1.5rem;
+}
+
+.seo-hub-links {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
+ gap: 0.75rem 1rem;
+}
+
.seo-point {
display: flex;
flex-direction: column;
diff --git a/frontend/src/views/TableTennisClubSoftware.vue b/frontend/src/views/TableTennisClubSoftware.vue
new file mode 100644
index 00000000..a5ab7f32
--- /dev/null
+++ b/frontend/src/views/TableTennisClubSoftware.vue
@@ -0,0 +1,67 @@
+
+
+
+
+
diff --git a/frontend/src/views/TableTennisTournamentSoftwarePage.vue b/frontend/src/views/TableTennisTournamentSoftwarePage.vue
new file mode 100644
index 00000000..6415380b
--- /dev/null
+++ b/frontend/src/views/TableTennisTournamentSoftwarePage.vue
@@ -0,0 +1,66 @@
+
+
+
+
+
diff --git a/frontend/src/views/TrainingPlanningPage.vue b/frontend/src/views/TrainingPlanningPage.vue
new file mode 100644
index 00000000..600f2722
--- /dev/null
+++ b/frontend/src/views/TrainingPlanningPage.vue
@@ -0,0 +1,66 @@
+
+
+
+
+
diff --git a/update-sitemap.sh b/update-sitemap.sh
index 4d1b5d71..43ac76ef 100755
--- a/update-sitemap.sh
+++ b/update-sitemap.sh
@@ -18,6 +18,10 @@ echo "Aktualisiere lastmod-Datum auf: $TODAY"
URLS=(
"https://tt-tagebuch.de/"
"https://tt-tagebuch.de/impressum"
+ "https://tt-tagebuch.de/vereinssoftware-tischtennis"
+ "https://tt-tagebuch.de/mitgliederverwaltung-verein"
+ "https://tt-tagebuch.de/trainingsplanung-tischtennis"
+ "https://tt-tagebuch.de/turniersoftware-tischtennis"
"https://tt-tagebuch.de/datenschutz"
)
@@ -42,6 +46,30 @@ cat > "$SITEMAP_FILE" <
${URLS[2]}
${TODAY}
+ monthly
+ 0.8
+
+
+ ${URLS[3]}
+ ${TODAY}
+ monthly
+ 0.8
+
+
+ ${URLS[4]}
+ ${TODAY}
+ monthly
+ 0.8
+
+
+ ${URLS[5]}
+ ${TODAY}
+ monthly
+ 0.8
+
+
+ ${URLS[6]}
+ ${TODAY}
yearly
0.3