feat: add robots.txt and sitemap.xml routes for SEO optimization
All checks were successful
Code Analysis and Production Deploy / analyze (push) Successful in 7m44s
Code Analysis and Production Deploy / deploy-production (push) Has been skipped
Code Analysis and Production Deploy / deploy-test (push) Successful in 2m15s

- Implemented a new route for robots.txt to control crawler access.
- Added a sitemap.xml route to provide search engines with a list of site URLs.
- Included functions for URL normalization and XML escaping to ensure proper formatting.
This commit is contained in:
Torsten Schulz (local)
2026-05-31 13:36:49 +02:00
parent 31d20f1bff
commit 7c93966878
9 changed files with 269 additions and 39 deletions

View File

@@ -0,0 +1,30 @@
function normalizeBaseUrl(value) {
const raw = String(value || '').trim()
if (!raw) return ''
return raw.replace(/\/+$/, '')
}
export default defineEventHandler((event) => {
const runtimeConfig = useRuntimeConfig(event)
const requestUrl = getRequestURL(event)
const baseUrl = normalizeBaseUrl(runtimeConfig.public?.baseUrl) || `${requestUrl.protocol}//${requestUrl.host}`
const lines = [
'User-agent: *',
'Allow: /',
'Disallow: /cms',
'Disallow: /cms/',
'Disallow: /mitgliederbereich',
'Disallow: /mitgliederbereich/',
'Disallow: /api/',
'Disallow: /login',
'Disallow: /registrieren',
'Disallow: /passwort-vergessen',
'Disallow: /konto-loeschen',
`Sitemap: ${baseUrl}/sitemap.xml`
]
setHeader(event, 'Content-Type', 'text/plain; charset=utf-8')
return `${lines.join('\n')}\n`
})

View File

@@ -0,0 +1,87 @@
function normalizeBaseUrl(value) {
const raw = String(value || '').trim()
if (!raw) return ''
return raw.replace(/\/+$/, '')
}
function escapeXml(value) {
return String(value)
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;')
}
function toAbsoluteUrl(baseUrl, path) {
const normalizedPath = path.startsWith('/') ? path : `/${path}`
return `${baseUrl}${normalizedPath}`
}
export default defineEventHandler((event) => {
const runtimeConfig = useRuntimeConfig(event)
const requestUrl = getRequestURL(event)
const baseUrl = normalizeBaseUrl(runtimeConfig.public?.baseUrl) || `${requestUrl.protocol}//${requestUrl.host}`
const today = new Date().toISOString().slice(0, 10)
const routes = [
'/',
'/kontakt',
'/termine',
'/mitgliedschaft',
'/spielplan',
'/mannschaften',
'/mannschaften/spielplaene',
'/mannschaften/herren',
'/mannschaften/damen',
'/mannschaften/jugend',
'/training',
'/training/trainer',
'/training/anfaenger',
'/vereinsmeisterschaften',
'/spielsysteme',
'/links',
'/vorstand',
'/impressum',
'/datenschutz',
'/tt-regeln',
'/ueber-uns',
'/geschichte',
'/satzung',
'/galerie',
'/verein/ueber-uns',
'/verein/geschichte',
'/verein/satzung',
'/verein/tt-regeln',
'/verein/galerie',
'/newsletter/subscribe',
'/newsletter/unsubscribe'
]
const uniqueRoutes = [...new Set(routes)]
const entries = uniqueRoutes.map((route) => {
const loc = escapeXml(toAbsoluteUrl(baseUrl, route))
const priority = route === '/' ? '1.0' : '0.7'
return [
' <url>',
` <loc>${loc}</loc>`,
` <lastmod>${today}</lastmod>`,
' <changefreq>weekly</changefreq>',
` <priority>${priority}</priority>`,
' </url>'
].join('\n')
})
const xml = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
entries.join('\n'),
'</urlset>',
''
].join('\n')
setHeader(event, 'Content-Type', 'application/xml; charset=utf-8')
return xml
})