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 @@
+
+