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);
|
||||
});
|
||||
|
||||
@@ -15,6 +15,30 @@
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://tt-tagebuch.de/vereinssoftware-tischtennis</loc>
|
||||
<lastmod>2026-03-27</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://tt-tagebuch.de/mitgliederverwaltung-verein</loc>
|
||||
<lastmod>2026-03-27</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://tt-tagebuch.de/trainingsplanung-tischtennis</loc>
|
||||
<lastmod>2026-03-27</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://tt-tagebuch.de/turniersoftware-tischtennis</loc>
|
||||
<lastmod>2026-03-27</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://tt-tagebuch.de/datenschutz</loc>
|
||||
<lastmod>2026-03-27</lastmod>
|
||||
|
||||
215
frontend/src/components/SeoLandingPage.vue
Normal file
215
frontend/src/components/SeoLandingPage.vue
Normal file
@@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<div class="seo-landing-page">
|
||||
<section class="seo-hero card">
|
||||
<p class="seo-eyebrow">{{ eyebrow }}</p>
|
||||
<h1>{{ title }}</h1>
|
||||
<p class="seo-lead">{{ lead }}</p>
|
||||
<div v-if="highlights?.length" class="seo-highlights">
|
||||
<span v-for="highlight in highlights" :key="highlight" class="seo-highlight-chip">
|
||||
{{ highlight }}
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="seo-body-grid">
|
||||
<article class="card seo-main-card">
|
||||
<div v-for="section in sections" :key="section.heading" class="seo-section">
|
||||
<h2>{{ section.heading }}</h2>
|
||||
<p v-for="paragraph in section.paragraphs" :key="paragraph">
|
||||
{{ paragraph }}
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<aside class="card seo-side-card">
|
||||
<div class="seo-side-block">
|
||||
<h2>Wichtig im Überblick</h2>
|
||||
<ul>
|
||||
<li v-for="point in checklist" :key="point">{{ point }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="seo-side-block">
|
||||
<h2>Passende Seiten</h2>
|
||||
<nav class="seo-related-links">
|
||||
<router-link
|
||||
v-for="link in relatedLinks"
|
||||
:key="link.to"
|
||||
:to="link.to"
|
||||
class="seo-related-link"
|
||||
>
|
||||
{{ link.label }}
|
||||
</router-link>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="seo-side-block">
|
||||
<h2>Nächster Schritt</h2>
|
||||
<p>Wer die Abläufe im Verein bündeln will, kann die Anwendung direkt im Browser testen.</p>
|
||||
<div class="seo-cta-links">
|
||||
<router-link to="/register" class="btn-primary">Kostenlos starten</router-link>
|
||||
<router-link to="/login" class="btn-secondary">Zum Login</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SeoLandingPage',
|
||||
props: {
|
||||
eyebrow: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
lead: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
highlights: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
sections: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
checklist: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
relatedLinks: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.seo-landing-page {
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.seo-hero {
|
||||
padding: 1.75rem;
|
||||
border: 1px solid rgba(47, 122, 95, 0.18);
|
||||
background: linear-gradient(135deg, rgba(47, 122, 95, 0.08), rgba(255, 255, 255, 0.98));
|
||||
}
|
||||
|
||||
.seo-eyebrow {
|
||||
margin: 0 0 0.5rem 0;
|
||||
color: var(--primary-color);
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.seo-hero h1 {
|
||||
margin: 0;
|
||||
font-size: 2rem;
|
||||
line-height: 1.2;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.seo-lead {
|
||||
margin: 1rem 0 0 0;
|
||||
font-size: 1.05rem;
|
||||
line-height: 1.7;
|
||||
color: var(--text-secondary);
|
||||
max-width: 860px;
|
||||
}
|
||||
|
||||
.seo-highlights {
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
.seo-highlight-chip {
|
||||
padding: 0.45rem 0.8rem;
|
||||
border-radius: 999px;
|
||||
background: rgba(47, 122, 95, 0.1);
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.seo-body-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.8fr) minmax(280px, 1fr);
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.seo-main-card,
|
||||
.seo-side-card {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.seo-section + .seo-section {
|
||||
margin-top: 1.6rem;
|
||||
}
|
||||
|
||||
.seo-section h2,
|
||||
.seo-side-block h2 {
|
||||
margin: 0 0 0.75rem 0;
|
||||
color: var(--text-primary);
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.seo-section p,
|
||||
.seo-side-block p,
|
||||
.seo-side-block li {
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.seo-side-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.seo-side-block ul {
|
||||
margin: 0;
|
||||
padding-left: 1.1rem;
|
||||
}
|
||||
|
||||
.seo-related-links,
|
||||
.seo-cta-links {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.seo-related-link {
|
||||
color: var(--primary-color);
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.seo-related-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.seo-body-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.seo-hero h1 {
|
||||
font-size: 1.65rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -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 },
|
||||
|
||||
@@ -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.',
|
||||
|
||||
66
frontend/src/views/ClubMemberManagementPage.vue
Normal file
66
frontend/src/views/ClubMemberManagementPage.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<SeoLandingPage
|
||||
eyebrow="Mitgliederverwaltung"
|
||||
title="Mitgliederverwaltung für Vereine"
|
||||
lead="Die Mitgliederverwaltung im Trainingstagebuch hilft Vereinen dabei, Stammdaten, Status, Gruppen, Rollen und sportbezogene Zuordnungen konsistent an einem Ort zu pflegen."
|
||||
:highlights="highlights"
|
||||
:sections="sections"
|
||||
:checklist="checklist"
|
||||
:related-links="relatedLinks"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SeoLandingPage from '../components/SeoLandingPage.vue';
|
||||
|
||||
export default {
|
||||
name: 'ClubMemberManagementPage',
|
||||
components: {
|
||||
SeoLandingPage,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
highlights: [
|
||||
'Kontaktdaten',
|
||||
'Status und Rollen',
|
||||
'Gruppenzuordnung',
|
||||
'Trainingsbezug',
|
||||
],
|
||||
sections: [
|
||||
{
|
||||
heading: 'Mitgliederdaten als Grundlage der Vereinsarbeit',
|
||||
paragraphs: [
|
||||
'Ohne saubere Mitgliederdaten wird jede weitere Organisation unnötig aufwendig. Genau deshalb ist die Mitgliederverwaltung nicht nur ein Adressverzeichnis, sondern die Basis für Trainingsgruppen, Teilnahmen, Auswertungen und interne Rechte.',
|
||||
'Im Trainingstagebuch lassen sich aktive Mitglieder, Testmitglieder, Status, Kontaktdaten und Vereinsrollen in einem gemeinsamen System verwalten. Das reduziert doppelte Pflege und verhindert, dass dieselbe Information in mehreren Listen auseinanderläuft.',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Relevanz für Training und Gruppen',
|
||||
paragraphs: [
|
||||
'Gerade im Tischtennis hängen Mitgliedsdaten oft direkt an Trainingsgruppen, Trainingstagen und Mannschaften. Wenn die Zuordnung sauber gepflegt ist, lassen sich Anwesenheiten, Aktivitäten und organisatorische Abläufe nachvollziehbar steuern.',
|
||||
'Diese Seite ist deshalb nicht nur für allgemeine Suchanfragen nach Mitgliederverwaltung interessant, sondern speziell für Vereine, die ihre Trainingsorganisation mit den Stammdaten verzahnen wollen.',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Datenschutz und Rollen',
|
||||
paragraphs: [
|
||||
'Mitgliederverwaltung ist immer auch ein Datenschutz-Thema. Deshalb spielt die Frage eine Rolle, wer welche Daten sehen oder ändern darf. Das System ist auf rollenbasierte Zugriffe ausgelegt, damit nicht jede Person im Verein automatisch Vollzugriff erhält.',
|
||||
'Für SEO ist das ein relevanter Vertrauensfaktor: Nicht nur Funktionen zählen, sondern auch der nachvollziehbare Umgang mit personenbezogenen Vereinsdaten.',
|
||||
],
|
||||
},
|
||||
],
|
||||
checklist: [
|
||||
'Mitglieder, Probemitglieder und Status sauber abbilden',
|
||||
'Kontaktdaten und Rollen zentral pflegen',
|
||||
'Direkte Verbindung zu Trainingsgruppen und Abläufen',
|
||||
'Datensparsamkeit und Zugriffssteuerung berücksichtigen',
|
||||
],
|
||||
relatedLinks: [
|
||||
{ to: '/vereinssoftware-tischtennis', label: 'Vereinssoftware für Tischtennisvereine' },
|
||||
{ to: '/trainingsplanung-tischtennis', label: 'Trainingsplanung im Tischtennis' },
|
||||
{ to: '/turniersoftware-tischtennis', label: 'Turniersoftware für Tischtennis' },
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -85,6 +85,9 @@
|
||||
Trainingsgruppen, Teilnahmen und Team-Zuordnungen direkt mit den Mitgliedsdaten
|
||||
verknüpfen.
|
||||
</p>
|
||||
<router-link to="/mitgliederverwaltung-verein" class="topic-link">
|
||||
Mehr zur Mitgliederverwaltung
|
||||
</router-link>
|
||||
</article>
|
||||
<article class="search-topic card">
|
||||
<h2>Trainingsplanung und Trainingstagebuch</h2>
|
||||
@@ -93,6 +96,9 @@
|
||||
halten Aktivitäten im Trainingstagebuch fest. Das eignet sich besonders für
|
||||
Tischtennistraining mit wechselnden Gruppen, Anwesenheiten und Stationsformen.
|
||||
</p>
|
||||
<router-link to="/trainingsplanung-tischtennis" class="topic-link">
|
||||
Mehr zur Trainingsplanung
|
||||
</router-link>
|
||||
</article>
|
||||
<article class="search-topic card">
|
||||
<h2>Turniersoftware für Vereinswettbewerbe</h2>
|
||||
@@ -101,6 +107,9 @@
|
||||
Wettbewerbe. Teilnehmer, Gruppen, Auslosungen, Ergebnisse und Übersichten lassen
|
||||
sich in einer Anwendung verwalten.
|
||||
</p>
|
||||
<router-link to="/turniersoftware-tischtennis" class="topic-link">
|
||||
Mehr zur Turniersoftware
|
||||
</router-link>
|
||||
</article>
|
||||
<article class="search-topic card">
|
||||
<h2>Team-Management und Statistiken</h2>
|
||||
@@ -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.
|
||||
</p>
|
||||
<router-link to="/vereinssoftware-tischtennis" class="topic-link">
|
||||
Mehr zur Vereinssoftware
|
||||
</router-link>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="seo-hub card">
|
||||
<h3 class="section-title">Öffentliche Themenseiten</h3>
|
||||
<div class="seo-hub-links">
|
||||
<router-link to="/vereinssoftware-tischtennis" class="seo-hub-link">
|
||||
Vereinssoftware für Tischtennisvereine
|
||||
</router-link>
|
||||
<router-link to="/mitgliederverwaltung-verein" class="seo-hub-link">
|
||||
Mitgliederverwaltung für Vereine
|
||||
</router-link>
|
||||
<router-link to="/trainingsplanung-tischtennis" class="seo-hub-link">
|
||||
Trainingsplanung für Tischtennisvereine
|
||||
</router-link>
|
||||
<router-link to="/turniersoftware-tischtennis" class="seo-hub-link">
|
||||
Turniersoftware für Tischtennis
|
||||
</router-link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="features-section">
|
||||
<h3 class="section-title">{{ $t('home.whatCanYouDo') }}</h3>
|
||||
<div class="features-grid">
|
||||
@@ -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;
|
||||
|
||||
67
frontend/src/views/TableTennisClubSoftware.vue
Normal file
67
frontend/src/views/TableTennisClubSoftware.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<SeoLandingPage
|
||||
eyebrow="Öffentliche Infoseite"
|
||||
title="Vereinssoftware für Tischtennisvereine"
|
||||
lead="Trainingstagebuch ist eine webbasierte Vereinssoftware für Tischtennisvereine, die Mitgliederverwaltung, Trainingsplanung, Mannschaftsbetrieb, Turniere und Auswertungen in einem System bündelt."
|
||||
:highlights="highlights"
|
||||
:sections="sections"
|
||||
:checklist="checklist"
|
||||
:related-links="relatedLinks"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SeoLandingPage from '../components/SeoLandingPage.vue';
|
||||
|
||||
export default {
|
||||
name: 'TableTennisClubSoftware',
|
||||
components: {
|
||||
SeoLandingPage,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
highlights: [
|
||||
'Mitgliederverwaltung',
|
||||
'Trainingsplanung',
|
||||
'Turniere',
|
||||
'Mannschaften',
|
||||
'MyTischtennis-Integration',
|
||||
],
|
||||
sections: [
|
||||
{
|
||||
heading: 'Wofür die Software gedacht ist',
|
||||
paragraphs: [
|
||||
'Viele Vereine arbeiten gleichzeitig mit Tabellen, Messenger-Nachrichten, Einzeltools und Papierlisten. Genau dort entsteht Reibung: Trainingsgruppen stimmen nicht mit Mitgliedsdaten überein, Turnierlisten werden separat gepflegt und Mannschaftsinformationen liegen an anderer Stelle.',
|
||||
'Trainingstagebuch ist auf diese Vereinsrealität zugeschnitten. Die Anwendung verbindet typische Abläufe im Tischtennisverein in einer Oberfläche: Wer trainiert wann, wer gehört zu welcher Gruppe, welche Mannschaften gibt es, welche Turniere laufen und welche Daten müssen Trainer, Jugendwarte oder Vorstände sehen können.',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Warum das für Tischtennisvereine relevant ist',
|
||||
paragraphs: [
|
||||
'Tischtennisvereine haben andere Abläufe als generische Freizeitteams. Gruppen wechseln, Kinder und Erwachsene trainieren parallel, Mannschaften brauchen eigene Organisation, und offizielle Datenquellen wie MyTischtennis oder click-TT spielen in vielen Vereinen eine Rolle.',
|
||||
'Deshalb ist hier nicht nur eine allgemeine Vereinsverwaltung gemeint, sondern eine Vereinssoftware, die den Sportkontext berücksichtigt. Genau das macht die Seite auch für Suchanfragen rund um Tischtennisvereine und digitale Vereinsorganisation relevant.',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Wie der Einstieg aussieht',
|
||||
paragraphs: [
|
||||
'Die Anwendung läuft im Browser und benötigt keine lokale Installation. Dadurch kann ein Verein die Software auf Desktop, Tablet und Smartphone verwenden. Trainerteams und Vorstände können mit abgestuften Rechten arbeiten, ohne dass jede Person auf alle Daten Zugriff erhält.',
|
||||
'Für den organischen SEO-Aufbau ist diese Seite bewusst als öffentliche Landingpage formuliert: klarer Themenfokus, konkrete Begriffe, interne Verlinkung auf passende Unterseiten und ein eindeutiger Produktbezug.',
|
||||
],
|
||||
},
|
||||
],
|
||||
checklist: [
|
||||
'Zentrale Vereinsdaten statt verteilter Listen',
|
||||
'Browserbasiert auf Desktop und Mobilgeräten',
|
||||
'Rollen und Berechtigungen für Vereinsfunktionen',
|
||||
'Spezifisch für Abläufe im Tischtennisverein',
|
||||
],
|
||||
relatedLinks: [
|
||||
{ to: '/mitgliederverwaltung-verein', label: 'Mitgliederverwaltung für Vereine' },
|
||||
{ to: '/trainingsplanung-tischtennis', label: 'Trainingsplanung im Tischtennis' },
|
||||
{ to: '/turniersoftware-tischtennis', label: 'Turniersoftware für Tischtennis' },
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
66
frontend/src/views/TableTennisTournamentSoftwarePage.vue
Normal file
66
frontend/src/views/TableTennisTournamentSoftwarePage.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<SeoLandingPage
|
||||
eyebrow="Turniersoftware"
|
||||
title="Turniersoftware für Tischtennis"
|
||||
lead="Trainingstagebuch unterstützt Tischtennisvereine bei der Organisation interner, offener und offizieller Turniere mit Teilnehmerverwaltung, Gruppen, Paarungen, Ergebnissen und Übersichten."
|
||||
:highlights="highlights"
|
||||
:sections="sections"
|
||||
:checklist="checklist"
|
||||
:related-links="relatedLinks"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SeoLandingPage from '../components/SeoLandingPage.vue';
|
||||
|
||||
export default {
|
||||
name: 'TableTennisTournamentSoftwarePage',
|
||||
components: {
|
||||
SeoLandingPage,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
highlights: [
|
||||
'Interne Turniere',
|
||||
'Offene Turniere',
|
||||
'Offizielle Turniere',
|
||||
'Gruppen und Paarungen',
|
||||
],
|
||||
sections: [
|
||||
{
|
||||
heading: 'Turniere im Verein digital organisieren',
|
||||
paragraphs: [
|
||||
'Viele Vereinsturniere starten mit spontanen Listen und enden in händischen Anpassungen bei Gruppen, Auslosungen und Ergebnissen. Das kostet Zeit und ist fehleranfällig, vor allem wenn Teilnehmer kurzfristig wechseln oder Doppelkonstellationen gepflegt werden müssen.',
|
||||
'Mit einer spezialisierten Turniersoftware lassen sich Teilnehmer, Klassen, Gruppen und Turnierverläufe in einer strukturierten Oberfläche verwalten. Genau auf diesen organisatorischen Bedarf zielt Trainingstagebuch ab.',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Relevant für interne und offizielle Formate',
|
||||
paragraphs: [
|
||||
'Nicht jedes Turnier im Verein ist gleich. Manche Wettbewerbe sind intern, andere offen oder offiziell. Deshalb ist es sinnvoll, nicht nur eine starre Turniermaske zu haben, sondern unterschiedliche Formate im selben System abzubilden.',
|
||||
'Für SEO deckt diese Seite gezielt Suchanfragen zu Turniersoftware, Tischtennisturnieren im Verein und digitaler Turnierorganisation ab.',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Warum die Kombination mit Vereinsdaten hilft',
|
||||
paragraphs: [
|
||||
'Wenn Teilnehmerdaten, Mitgliedsbezug und Turnierverwaltung in derselben Anwendung liegen, wird der Ablauf deutlich einfacher. Namen, Gruppenbezug und organisatorische Informationen müssen nicht aus mehreren Quellen zusammengetragen werden.',
|
||||
'Das ist ein praktischer Vorteil im Alltag und zugleich ein starker thematischer Fokus für die öffentliche Suche.',
|
||||
],
|
||||
},
|
||||
],
|
||||
checklist: [
|
||||
'Teilnehmer und Klassen in einer Oberfläche verwalten',
|
||||
'Gruppen, Paarungen und Ergebnisse digital pflegen',
|
||||
'Geeignet für interne, offene und offizielle Formate',
|
||||
'Enger Bezug zur Vereinsorganisation statt Einzellösung',
|
||||
],
|
||||
relatedLinks: [
|
||||
{ to: '/vereinssoftware-tischtennis', label: 'Vereinssoftware für Tischtennisvereine' },
|
||||
{ to: '/mitgliederverwaltung-verein', label: 'Mitgliederverwaltung für Vereine' },
|
||||
{ to: '/trainingsplanung-tischtennis', label: 'Trainingsplanung im Tischtennis' },
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
66
frontend/src/views/TrainingPlanningPage.vue
Normal file
66
frontend/src/views/TrainingPlanningPage.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<SeoLandingPage
|
||||
eyebrow="Trainingsplanung"
|
||||
title="Trainingsplanung für Tischtennisvereine"
|
||||
lead="Von Trainingsgruppen über Anwesenheiten bis zum Trainingstagebuch: Die Trainingsplanung im Trainingstagebuch unterstützt Vereine dabei, ihr Tischtennistraining strukturiert und nachvollziehbar zu organisieren."
|
||||
:highlights="highlights"
|
||||
:sections="sections"
|
||||
:checklist="checklist"
|
||||
:related-links="relatedLinks"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SeoLandingPage from '../components/SeoLandingPage.vue';
|
||||
|
||||
export default {
|
||||
name: 'TrainingPlanningPage',
|
||||
components: {
|
||||
SeoLandingPage,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
highlights: [
|
||||
'Trainingsgruppen',
|
||||
'Anwesenheiten',
|
||||
'Trainingstagebuch',
|
||||
'Aktivitäten',
|
||||
],
|
||||
sections: [
|
||||
{
|
||||
heading: 'Trainingsplanung ist mehr als ein Kalender',
|
||||
paragraphs: [
|
||||
'In vielen Vereinen bedeutet Trainingsplanung nicht nur, einen Termin einzutragen. Es geht darum, Gruppen sinnvoll zu besetzen, Anwesenheiten zu dokumentieren, Inhalte festzuhalten und über längere Zeit nachvollziehen zu können, wie Training tatsächlich organisiert wurde.',
|
||||
'Genau dafür kombiniert Trainingstagebuch Terminbezug, Gruppensteuerung und Dokumentation. So entsteht aus der Planung nicht nur eine Ankündigung, sondern ein belastbarer Ablauf mit nachvollziehbaren Daten.',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Warum das speziell im Tischtennis wichtig ist',
|
||||
paragraphs: [
|
||||
'Tischtennistraining arbeitet oft mit wechselnden Leistungsständen, parallelen Hallenzeiten, Jugend- und Erwachsenengruppen sowie kurzfristigen Anpassungen. Wer diese Abläufe mit losen Listen organisiert, verliert schnell den Überblick.',
|
||||
'Eine spezialisierte Lösung für Trainingsplanung im Tischtennis hilft dabei, Gruppenwechsel, Anwesenheiten und Trainingsaktivitäten sauber zu dokumentieren. Das ist sowohl organisatorisch hilfreich als auch für die langfristige Entwicklung im Verein wertvoll.',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Nutzen für Trainerteams und Vereinsleitung',
|
||||
paragraphs: [
|
||||
'Trainer profitieren von klaren Gruppenstrukturen und dokumentierten Trainingstagen. Vereinsverantwortliche profitieren davon, dass die Informationen nicht nur im Kopf oder in privaten Chats einzelner Personen liegen.',
|
||||
'Für Suchmaschinen ist diese Seite ein klarer thematischer Einstieg zu Trainingsplanung, Trainingstagebuch und digitaler Trainingsorganisation im Vereinsumfeld.',
|
||||
],
|
||||
},
|
||||
],
|
||||
checklist: [
|
||||
'Gruppen und Teilnahmen im Blick behalten',
|
||||
'Trainingstage strukturiert dokumentieren',
|
||||
'Anwesenheiten und Inhalte nachvollziehbar speichern',
|
||||
'Für Jugend- und Erwachsenentraining geeignet',
|
||||
],
|
||||
relatedLinks: [
|
||||
{ to: '/vereinssoftware-tischtennis', label: 'Vereinssoftware für Tischtennisvereine' },
|
||||
{ to: '/mitgliederverwaltung-verein', label: 'Mitgliederverwaltung für Vereine' },
|
||||
{ to: '/turniersoftware-tischtennis', label: 'Turniersoftware für Tischtennis' },
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -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" <<EOF
|
||||
<url>
|
||||
<loc>${URLS[2]}</loc>
|
||||
<lastmod>${TODAY}</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>${URLS[3]}</loc>
|
||||
<lastmod>${TODAY}</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>${URLS[4]}</loc>
|
||||
<lastmod>${TODAY}</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>${URLS[5]}</loc>
|
||||
<lastmod>${TODAY}</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>${URLS[6]}</loc>
|
||||
<lastmod>${TODAY}</lastmod>
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
|
||||
Reference in New Issue
Block a user