diff --git a/web/src/routes/markt/[slug]/+page.svelte b/web/src/routes/markt/[slug]/+page.svelte
index cea0e07..2072a09 100644
--- a/web/src/routes/markt/[slug]/+page.svelte
+++ b/web/src/routes/markt/[slug]/+page.svelte
@@ -36,8 +36,46 @@
diff --git a/web/src/routes/sitemap.xml/+server.ts b/web/src/routes/sitemap.xml/+server.ts
new file mode 100644
index 0000000..75940c0
--- /dev/null
+++ b/web/src/routes/sitemap.xml/+server.ts
@@ -0,0 +1,55 @@
+import type { RequestHandler } from './$types.js';
+import { apiFetch } from '$lib/api/client.js';
+import type { MarketSummary } from '$lib/api/types.js';
+
+const ORIGIN = 'https://marktvogt.de';
+
+function escapeXml(str: string): string {
+ return str.replace(/&/g, '&').replace(//g, '>');
+}
+
+export const GET: RequestHandler = async ({ fetch }) => {
+ const staticPages = [
+ { path: '/', priority: '1.0', changefreq: 'daily' },
+ { path: '/impressum', priority: '0.2', changefreq: 'yearly' },
+ { path: '/datenschutz', priority: '0.2', changefreq: 'yearly' }
+ ];
+
+ let markets: MarketSummary[] = [];
+ try {
+ const res = await apiFetch
('/markets?per_page=1000', { fetch });
+ markets = res.data;
+ } catch {
+ // Backend unreachable — return static pages only
+ }
+
+ const urls = staticPages
+ .map(
+ (p) => `
+ ${ORIGIN}${p.path}
+ ${p.changefreq}
+ ${p.priority}
+ `
+ )
+ .concat(
+ markets.map(
+ (m) => `
+ ${ORIGIN}/markt/${escapeXml(m.slug)}
+ weekly
+ 0.8
+ `
+ )
+ );
+
+ const xml = `
+
+${urls.join('\n')}
+`;
+
+ return new Response(xml, {
+ headers: {
+ 'Content-Type': 'application/xml',
+ 'Cache-Control': 'max-age=3600'
+ }
+ });
+};
diff --git a/web/static/robots.txt b/web/static/robots.txt
index b6dd667..18b0899 100644
--- a/web/static/robots.txt
+++ b/web/static/robots.txt
@@ -1,3 +1,5 @@
# allow crawling everything by default
User-agent: *
Disallow:
+
+Sitemap: https://marktvogt.de/sitemap.xml