feat(Sitemap, SEO): update sitemap generation and SEO configurations

- Enhanced update-sitemap.sh to generate a new sitemap structure with lastmod dates for additional URLs.
- Updated SEO configurations in server.js and seo.js to allow indexing for impressum and datenschutz pages.
- Introduced a list of noindex prefixes to manage SEO settings dynamically based on route paths.
- Added structured FAQ schema in index.html to improve search engine visibility and user engagement.
This commit is contained in:
Torsten Schulz (local)
2026-03-27 11:22:55 +01:00
parent ddb3025b84
commit a7d3e5b094
5 changed files with 162 additions and 15 deletions

View File

@@ -111,15 +111,38 @@ const SEO_ROUTE_CONFIG = {
'/impressum': {
title: 'Impressum | Trainingstagebuch',
description: 'Impressum von Trainingstagebuch.',
robots: 'noindex,follow',
robots: 'index,follow',
},
'/datenschutz': {
title: 'Datenschutzerklärung | Trainingstagebuch',
description: 'Datenschutzerklärung von Trainingstagebuch.',
robots: 'noindex,follow',
robots: 'index,follow',
},
};
const SEO_NOINDEX_PREFIXES = [
'/createclub',
'/showclub',
'/members',
'/diary',
'/pending-approvals',
'/schedule',
'/tournaments',
'/tournament-participations',
'/training-stats',
'/club-settings',
'/predefined-activities',
'/mytischtennis-account',
'/clicktt-account',
'/team-management',
'/permissions',
'/logs',
'/clicktt',
'/member-transfer-settings',
'/personal-settings',
'/orders',
];
function normalizeSeoPath(pathname = '/') {
if (!pathname || pathname === '') return '/';
if (pathname === '/') return '/';
@@ -132,9 +155,16 @@ function getSeoConfigForPath(pathname = '/') {
.filter((routePath) => routePath !== '/' && normalizedPath.startsWith(routePath))
.sort((a, b) => b.length - a.length)[0];
return (matchedPrefix && SEO_ROUTE_CONFIG[matchedPrefix])
|| SEO_ROUTE_CONFIG[normalizedPath]
|| { ...SEO_DEFAULTS, robots: 'noindex,follow' };
const configuredSeo = (matchedPrefix && SEO_ROUTE_CONFIG[matchedPrefix]) || SEO_ROUTE_CONFIG[normalizedPath];
if (configuredSeo) {
return configuredSeo;
}
const shouldNoindex = SEO_NOINDEX_PREFIXES.some((routePath) => normalizedPath.startsWith(routePath));
return {
...SEO_DEFAULTS,
robots: shouldNoindex ? 'noindex,follow' : SEO_DEFAULTS.robots
};
}
function escapeHtmlAttribute(value = '') {

View File

@@ -72,6 +72,54 @@
"url": "https://tt-tagebuch.de/"
}
</script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Ist die Nutzung kostenlos?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Ja, du kannst kostenlos starten. Erweiterungen können später folgen."
}
},
{
"@type": "Question",
"name": "Wie steht es um den Datenschutz?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Wir setzen auf Datensparsamkeit, transparente Freigaben, rollenbasierte Zugriffe und vollständiges Aktivitätsprotokoll. Die Anwendung ist DSGVO-konform."
}
},
{
"@type": "Question",
"name": "Benötige ich eine Installation?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Nein, es handelt sich um eine Web-Anwendung. Du nutzt sie direkt im Browser auf Desktop, Tablet und Smartphone."
}
},
{
"@type": "Question",
"name": "Welche Turnierarten werden unterstützt?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Du kannst interne Turniere, offene Turniere und offizielle Turniere verwalten. Offizielle Turniere können importiert werden."
}
},
{
"@type": "Question",
"name": "Funktioniert die MyTischtennis-Integration automatisch?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Ja, nach der Einrichtung synchronisiert sich die Anwendung automatisch mit MyTischtennis.de und importiert Spielergebnisse und Statistiken."
}
}
]
}
</script>
</head>
<body>
<div id="app"></div>

View File

@@ -5,8 +5,20 @@
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
<url>
<loc>https://tt-tagebuch.de/</loc>
<lastmod>2026-03-18</lastmod>
<lastmod>2026-03-27</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://tt-tagebuch.de/impressum</loc>
<lastmod>2026-03-27</lastmod>
<changefreq>yearly</changefreq>
<priority>0.3</priority>
</url>
<url>
<loc>https://tt-tagebuch.de/datenschutz</loc>
<lastmod>2026-03-27</lastmod>
<changefreq>yearly</changefreq>
<priority>0.3</priority>
</url>
</urlset>

View File

@@ -42,15 +42,38 @@ const ROUTE_SEO = {
'/impressum': {
title: 'Impressum | Trainingstagebuch',
description: 'Impressum von Trainingstagebuch.',
robots: 'noindex,follow'
robots: 'index,follow'
},
'/datenschutz': {
title: 'Datenschutzerklärung | Trainingstagebuch',
description: 'Datenschutzerklärung von Trainingstagebuch.',
robots: 'noindex,follow'
robots: 'index,follow'
}
};
const NOINDEX_PREFIXES = [
'/createclub',
'/showclub',
'/members',
'/diary',
'/pending-approvals',
'/schedule',
'/tournaments',
'/tournament-participations',
'/training-stats',
'/club-settings',
'/predefined-activities',
'/mytischtennis-account',
'/clicktt-account',
'/team-management',
'/permissions',
'/logs',
'/clicktt',
'/member-transfer-settings',
'/personal-settings',
'/orders'
];
function normalizePath(path = '/') {
if (!path || path === '') return '/';
if (path === '/') return '/';
@@ -63,13 +86,18 @@ export function getSeoConfigForPath(path) {
.filter((routePath) => routePath !== '/' && normalizedPath.startsWith(routePath))
.sort((a, b) => b.length - a.length)[0];
const routeSeo = (matchedPrefix && ROUTE_SEO[matchedPrefix]) || ROUTE_SEO[normalizedPath] || DEFAULT_SEO;
const routeSeo = (matchedPrefix && ROUTE_SEO[matchedPrefix]) || ROUTE_SEO[normalizedPath];
const canonicalPath = normalizedPath === '/' ? '' : normalizedPath;
const shouldNoindex = !routeSeo && NOINDEX_PREFIXES.some((routePath) => normalizedPath.startsWith(routePath));
const finalSeo = routeSeo || {
...DEFAULT_SEO,
robots: shouldNoindex ? 'noindex,follow' : DEFAULT_SEO.robots
};
return {
title: routeSeo.title || DEFAULT_SEO.title,
description: routeSeo.description || DEFAULT_SEO.description,
robots: routeSeo.robots || DEFAULT_SEO.robots,
title: finalSeo.title || DEFAULT_SEO.title,
description: finalSeo.description || DEFAULT_SEO.description,
robots: finalSeo.robots || DEFAULT_SEO.robots,
canonical: `${SITE_URL}${canonicalPath}`,
url: `${SITE_URL}${canonicalPath}`,
image: DEFAULT_IMAGE

View File

@@ -15,8 +15,38 @@ fi
echo "Aktualisiere lastmod-Datum auf: $TODAY"
# Ersetze alle lastmod-Daten mit dem heutigen Datum
sed -i "s/<lastmod>.*<\/lastmod>/<lastmod>${TODAY}<\/lastmod>/g" "$SITEMAP_FILE"
URLS=(
"https://tt-tagebuch.de/"
"https://tt-tagebuch.de/impressum"
"https://tt-tagebuch.de/datenschutz"
)
cat > "$SITEMAP_FILE" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
<url>
<loc>${URLS[0]}</loc>
<lastmod>${TODAY}</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>${URLS[1]}</loc>
<lastmod>${TODAY}</lastmod>
<changefreq>yearly</changefreq>
<priority>0.3</priority>
</url>
<url>
<loc>${URLS[2]}</loc>
<lastmod>${TODAY}</lastmod>
<changefreq>yearly</changefreq>
<priority>0.3</priority>
</url>
</urlset>
EOF
echo "✓ Sitemap aktualisiert"
echo ""
@@ -31,4 +61,3 @@ echo " -> URL eingeben: https://tt-tagebuch.de/sitemap.xml"
echo ""
echo "3. Sitemap testen:"
echo " curl https://tt-tagebuch.de/sitemap.xml"