Merge pull request 'feat: add robots.txt and sitemap.xml routes for SEO optimization' (#39) from dev into main
Reviewed-on: #39
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
package de.harheimertc.ui.components
|
package de.harheimertc.ui.components
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.ScrollState
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.horizontalScroll
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
@@ -72,32 +74,62 @@ private fun CompactNavigation(
|
|||||||
onNavigate: (String) -> Unit,
|
onNavigate: (String) -> Unit,
|
||||||
navigationState: NavigationUiState = NavigationUiState(),
|
navigationState: NavigationUiState = NavigationUiState(),
|
||||||
) {
|
) {
|
||||||
val section = menuSection(selectedRoute)
|
val routeSection = menuSection(selectedRoute)
|
||||||
|
val sectionOverride = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf<MenuSection?>(null) }
|
||||||
|
val section = routeSection ?: sectionOverride.value
|
||||||
val subItems = submenu(section, navigationState)
|
val subItems = submenu(section, navigationState)
|
||||||
var cmsExpanded = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(false) }
|
var cmsExpanded = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(false) }
|
||||||
val navigateAndClose: (String) -> Unit = { route -> cmsExpanded.value = false; onNavigate(route) }
|
val navigateAndClose: (String) -> Unit = { route -> cmsExpanded.value = false; onNavigate(route) }
|
||||||
|
val mainScroll = rememberScrollState()
|
||||||
|
val subScroll = rememberScrollState()
|
||||||
|
val cmsSubScroll = rememberScrollState()
|
||||||
|
|
||||||
BrandRow(onLogin = { onNavigate(Destinations.Login.route) })
|
BrandRow(onLogin = { onNavigate(Destinations.Login.route) })
|
||||||
Row(
|
Box(modifier = Modifier.fillMaxWidth()) {
|
||||||
modifier = Modifier.horizontalScroll(rememberScrollState()),
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
modifier = Modifier.horizontalScroll(mainScroll),
|
||||||
) {
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
CompactLink("Start", Destinations.Home.route, selectedRoute, onNavigate)
|
) {
|
||||||
CompactLink("Verein", Destinations.VereinAbout.route, selectedRoute, onNavigate)
|
CompactLink("Start", Destinations.Home.route, selectedRoute, onNavigate) { sectionOverride.value = null }
|
||||||
CompactLink("Mannschaften", Destinations.Mannschaften.route, selectedRoute, onNavigate)
|
CompactSectionLink("Verein", MenuSection.VEREIN, section) { sectionOverride.value = MenuSection.VEREIN }
|
||||||
CompactLink("Training", Destinations.Training.route, selectedRoute, onNavigate)
|
CompactSectionLink("Mannschaften", MenuSection.MANNSCHAFTEN, section) { sectionOverride.value = MenuSection.MANNSCHAFTEN }
|
||||||
CompactLink("Termine", Destinations.Termine.route, selectedRoute, onNavigate)
|
CompactSectionLink("Training", MenuSection.TRAINING, section) { sectionOverride.value = MenuSection.TRAINING }
|
||||||
CompactLink("Spielplan", Destinations.Spielplan.route, selectedRoute, onNavigate)
|
CompactLink("Termine", Destinations.Termine.route, selectedRoute, onNavigate) { sectionOverride.value = null }
|
||||||
CompactLink("Newsletter", Destinations.NewsletterSubscribe.route, selectedRoute, onNavigate)
|
CompactLink("Spielplan", Destinations.Spielplan.route, selectedRoute, onNavigate) { sectionOverride.value = MenuSection.MANNSCHAFTEN }
|
||||||
if (navigationState.showGallery) {
|
CompactSectionLink("Newsletter", MenuSection.NEWSLETTER, section) { sectionOverride.value = MenuSection.NEWSLETTER }
|
||||||
CompactLink("Galerie", Destinations.Gallery.route, selectedRoute, onNavigate)
|
if (navigationState.showGallery) {
|
||||||
|
CompactLink("Galerie", Destinations.Gallery.route, selectedRoute, onNavigate) { sectionOverride.value = MenuSection.VEREIN }
|
||||||
|
}
|
||||||
|
if (navigationState.loggedIn) {
|
||||||
|
CompactSectionLink("Intern", MenuSection.INTERN, section) { sectionOverride.value = MenuSection.INTERN }
|
||||||
|
}
|
||||||
|
CompactLink("Kontakt", Destinations.Contact.route, selectedRoute, onNavigate) { sectionOverride.value = null }
|
||||||
|
if (navigationState.isAdmin || navigationState.canAccessNewsletter || navigationState.canAccessContactRequests) {
|
||||||
|
CompactSectionLink("CMS", MenuSection.INTERN, section) { sectionOverride.value = MenuSection.INTERN }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (navigationState.loggedIn) {
|
|
||||||
CompactLink("Intern", Destinations.MemberArea.route, selectedRoute, onNavigate)
|
if (mainScroll.canScrollBackward) {
|
||||||
|
Text(
|
||||||
|
"◀",
|
||||||
|
color = Color(0xFFD4D4D8),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterStart)
|
||||||
|
.background(Color(0x66000000), RoundedCornerShape(8.dp))
|
||||||
|
.padding(horizontal = 4.dp, vertical = 2.dp),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
CompactLink("Kontakt", Destinations.Contact.route, selectedRoute, onNavigate)
|
if (mainScroll.canScrollForward) {
|
||||||
if (navigationState.isAdmin || navigationState.canAccessNewsletter || navigationState.canAccessContactRequests) {
|
Text(
|
||||||
CompactLink("CMS", Destinations.Cms.route, selectedRoute, onNavigate)
|
"▶",
|
||||||
|
color = Color(0xFFD4D4D8),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterEnd)
|
||||||
|
.background(Color(0x66000000), RoundedCornerShape(8.dp))
|
||||||
|
.padding(horizontal = 4.dp, vertical = 2.dp),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +142,7 @@ private fun CompactNavigation(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.horizontalScroll(rememberScrollState())
|
.horizontalScroll(subScroll)
|
||||||
.padding(top = 3.dp),
|
.padding(top = 3.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
) {
|
) {
|
||||||
@@ -126,12 +158,15 @@ private fun CompactNavigation(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (subItems.isNotEmpty()) {
|
||||||
|
ScrollHintRow(subScroll)
|
||||||
|
}
|
||||||
|
|
||||||
if (cmsExpanded.value && cmsChildren.isNotEmpty()) {
|
if (cmsExpanded.value && cmsChildren.isNotEmpty()) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.horizontalScroll(rememberScrollState())
|
.horizontalScroll(cmsSubScroll)
|
||||||
.padding(top = 6.dp, bottom = 3.dp),
|
.padding(top = 6.dp, bottom = 3.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
) {
|
) {
|
||||||
@@ -139,6 +174,31 @@ private fun CompactNavigation(
|
|||||||
SubLink(child.label, child.route == selectedRoute) { onNavigate(child.route) }
|
SubLink(child.label, child.route == selectedRoute) { onNavigate(child.route) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ScrollHintRow(cmsSubScroll)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CompactSectionLink(
|
||||||
|
label: String,
|
||||||
|
section: MenuSection,
|
||||||
|
activeSection: MenuSection?,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onSelect: () -> Unit,
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
color = if (activeSection == section) Primary600 else Color.Transparent,
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
modifier = modifier.clickable { onSelect() },
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
color = if (activeSection == section) Color.White else Color(0xFFD4D4D8),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
modifier = Modifier.padding(vertical = 9.dp, horizontal = 2.dp),
|
||||||
|
maxLines = 1,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,11 +345,15 @@ private fun CompactLink(
|
|||||||
selectedRoute: String?,
|
selectedRoute: String?,
|
||||||
onNavigate: (String) -> Unit,
|
onNavigate: (String) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
beforeNavigate: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
color = if (route == selectedRoute) Primary600 else Color.Transparent,
|
color = if (route == selectedRoute) Primary600 else Color.Transparent,
|
||||||
shape = RoundedCornerShape(8.dp),
|
shape = RoundedCornerShape(8.dp),
|
||||||
modifier = modifier.clickable { onNavigate(route) },
|
modifier = modifier.clickable {
|
||||||
|
beforeNavigate()
|
||||||
|
onNavigate(route)
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
label,
|
label,
|
||||||
@@ -302,6 +366,30 @@ private fun CompactLink(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ScrollHintRow(scrollState: ScrollState) {
|
||||||
|
if (!scrollState.canScrollBackward && !scrollState.canScrollForward) return
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 2.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
if (scrollState.canScrollBackward) "◀" else "",
|
||||||
|
color = Color(0xFFD4D4D8),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
if (scrollState.canScrollForward) "▶" else "",
|
||||||
|
color = Color(0xFFD4D4D8),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SubLink(label: String, selected: Boolean, onClick: () -> Unit) {
|
private fun SubLink(label: String, selected: Boolean, onClick: () -> Unit) {
|
||||||
Surface(
|
Surface(
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ org.gradle.workers.max=2
|
|||||||
LOCAL_API_BASE_URL=https://harheimertc.tsschulz.de/
|
LOCAL_API_BASE_URL=https://harheimertc.tsschulz.de/
|
||||||
|
|
||||||
# Production backend for Play Store build variant
|
# Production backend for Play Store build variant
|
||||||
PRODUCTION_API_BASE_URL=https://harheimertc.tsschulz.de/
|
PRODUCTION_API_BASE_URL=https://harheimertc.de/
|
||||||
|
|
||||||
# Android app versioning for Play Store uploads
|
# Android app versioning for Play Store uploads
|
||||||
ANDROID_VERSION_CODE=17
|
ANDROID_VERSION_CODE=18
|
||||||
ANDROID_VERSION_NAME=0.9.12
|
ANDROID_VERSION_NAME=0.9.13
|
||||||
|
|
||||||
# Temporary hotfix: disable R8 minification for release to avoid Retrofit generic signature stripping.
|
# Temporary hotfix: disable R8 minification for release to avoid Retrofit generic signature stripping.
|
||||||
RELEASE_MINIFY_ENABLED=false
|
RELEASE_MINIFY_ENABLED=false
|
||||||
|
|||||||
@@ -7,11 +7,22 @@
|
|||||||
<div class="absolute inset-0 z-0">
|
<div class="absolute inset-0 z-0">
|
||||||
<div class="absolute top-0 right-0 w-96 h-96 bg-primary-200/30 rounded-full blur-3xl" />
|
<div class="absolute top-0 right-0 w-96 h-96 bg-primary-200/30 rounded-full blur-3xl" />
|
||||||
<div class="absolute bottom-0 left-0 w-96 h-96 bg-gray-300/30 rounded-full blur-3xl" />
|
<div class="absolute bottom-0 left-0 w-96 h-96 bg-gray-300/30 rounded-full blur-3xl" />
|
||||||
<!-- Hintergrundbild -->
|
<picture class="absolute inset-0 opacity-10">
|
||||||
<div
|
<source
|
||||||
class="absolute inset-0 opacity-10"
|
type="image/webp"
|
||||||
style="background-image: url('/images/club_about_us.png'); background-size: cover; background-position: center;"
|
srcset="/images/club_about_us_hero_960.webp 960w, /images/club_about_us_hero_1600.webp 1600w"
|
||||||
/>
|
sizes="(max-width: 1024px) 960px, 1600px"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="/images/club_about_us.png"
|
||||||
|
alt=""
|
||||||
|
aria-hidden="true"
|
||||||
|
class="w-full h-full object-cover"
|
||||||
|
loading="eager"
|
||||||
|
fetchpriority="high"
|
||||||
|
decoding="async"
|
||||||
|
>
|
||||||
|
</picture>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
|
|||||||
@@ -77,6 +77,20 @@ export default defineNuxtConfig({
|
|||||||
],
|
],
|
||||||
link: [
|
link: [
|
||||||
{ rel: 'canonical', href: 'https://www.harheimertc.de/' },
|
{ rel: 'canonical', href: 'https://www.harheimertc.de/' },
|
||||||
|
{
|
||||||
|
rel: 'preload',
|
||||||
|
as: 'image',
|
||||||
|
href: '/images/club_about_us_hero_960.webp',
|
||||||
|
type: 'image/webp',
|
||||||
|
media: '(max-width: 1024px)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rel: 'preload',
|
||||||
|
as: 'image',
|
||||||
|
href: '/images/club_about_us_hero_1600.webp',
|
||||||
|
type: 'image/webp',
|
||||||
|
media: '(min-width: 1025px)'
|
||||||
|
},
|
||||||
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
|
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
|
||||||
{ rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: '' },
|
{ rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: '' },
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -226,17 +226,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref } from 'vue'
|
import { computed, defineAsyncComponent, ref } from 'vue'
|
||||||
import { SlidersHorizontal, X } from 'lucide-vue-next'
|
import { SlidersHorizontal, X } from 'lucide-vue-next'
|
||||||
import Hero from '~/components/Hero.vue'
|
import Hero from '~/components/Hero.vue'
|
||||||
import HomeTermine from '~/components/HomeTermine.vue'
|
const HomeTermine = defineAsyncComponent(() => import('~/components/HomeTermine.vue'))
|
||||||
import Spielplan from '~/components/Spielplan.vue'
|
const Spielplan = defineAsyncComponent(() => import('~/components/Spielplan.vue'))
|
||||||
import PublicNews from '~/components/PublicNews.vue'
|
const PublicNews = defineAsyncComponent(() => import('~/components/PublicNews.vue'))
|
||||||
import HomeActions from '~/components/HomeActions.vue'
|
const HomeActions = defineAsyncComponent(() => import('~/components/HomeActions.vue'))
|
||||||
import HomeTrainingTeaser from '~/components/HomeTrainingTeaser.vue'
|
const HomeTrainingTeaser = defineAsyncComponent(() => import('~/components/HomeTrainingTeaser.vue'))
|
||||||
import HomeLinksTeaser from '~/components/HomeLinksTeaser.vue'
|
const HomeLinksTeaser = defineAsyncComponent(() => import('~/components/HomeLinksTeaser.vue'))
|
||||||
import HomeVereinsmeisterschaftenTeaser from '~/components/HomeVereinsmeisterschaftenTeaser.vue'
|
const HomeVereinsmeisterschaftenTeaser = defineAsyncComponent(() => import('~/components/HomeVereinsmeisterschaftenTeaser.vue'))
|
||||||
import HomeSpielplanTeamWidget from '~/components/HomeSpielplanTeamWidget.vue'
|
const HomeSpielplanTeamWidget = defineAsyncComponent(() => import('~/components/HomeSpielplanTeamWidget.vue'))
|
||||||
|
|
||||||
const { data: config } = await useFetch('/api/config')
|
const { data: config } = await useFetch('/api/config')
|
||||||
const { data: authStatus } = await useFetch('/api/auth/status')
|
const { data: authStatus } = await useFetch('/api/auth/status')
|
||||||
|
|||||||
BIN
public/images/club_about_us_hero_1600.webp
Normal file
BIN
public/images/club_about_us_hero_1600.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
BIN
public/images/club_about_us_hero_960.webp
Normal file
BIN
public/images/club_about_us_hero_960.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
30
server/routes/robots.txt.js
Normal file
30
server/routes/robots.txt.js
Normal 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`
|
||||||
|
})
|
||||||
87
server/routes/sitemap.xml.js
Normal file
87
server/routes/sitemap.xml.js
Normal 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, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''')
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user