Einfach Sitemaps in tanstack-start
Übersicht
Einfach Sitemaps in tanstack-start
Sitemap-Generierung mit @unom/vite-sitemap-plugin
Wer eine Vite-basierte App mit TanStack Start betreibt und ordentliche SEO-Grundlagen braucht, stößt schnell auf ein unbefriedigend gelöstes Problem: sitemap.xml und robots.txt. Der Sitemap-Support von TanStack Start ist zum Zeitpunkt dieses Artikels noch sehr rudimentär – keine i18n-Unterstützung, keine feingranulare Kontrolle über changefreq oder priority, und das Ergebnis landet oft irgendwo, wo man es gar nicht haben will.
Also habe ich @unom/vite-sitemap-plugin geschrieben – ein schlankes Vite-Plugin, das beide Dateien zuverlässig zur Build-Zeit erzeugt, vollständiges hreflang-Handling für mehrsprachige Seiten mitbringt, und sich mit wenigen Zeilen konfigurieren lässt.
Das Paket ist nicht auf npmjs.org veröffentlicht, sondern über meine selbst gehostete Gitea-Registry verfügbar. Die Quellen liegen unter git.unom.io/unom/vite-sitemap-plugin.
Was das Plugin macht
- Schreibt
sitemap.xmlim Sitemap-0.9-Format in das Output-Verzeichnis - Schreibt
robots.txtmit konfigurierbarenAllow/Disallow-Regeln - Unterstützt i18n-Routing mit
<xhtml:link rel="alternate" hreflang="…">für jede Locale/Pfad-Kombination - Erlaubt pro Route individuelle
changefreq-,priority- undlastmod-Werte - Lässt sich mit einer eigenen
formatUrl-Funktion vollständig anpassen - Keine Runtime-Abhängigkeiten, reines Build-Zeit-Plugin
Voraussetzungen
- Node.js ≥ 18 oder Bun
- Vite ≥ 4 (peer dependency)
Schritt 1: Registry konfigurieren
Da das Paket nicht auf npmjs.org liegt, muss die Gitea-Registry zuerst als Quelle für den @unom-Scope eingetragen werden.
Mit npm:
npm config set @unom:registry https://git.unom.io/api/packages/unom/npm/
Mit pnpm:
pnpm config set @unom:registry https://git.unom.io/api/packages/unom/npm/
Oder direkt in der .npmrc im Projekt-Root:
@unom:registry=https://git.unom.io/api/packages/unom/npm/
Damit weiß der Paketmanager, dass alle Pakete unter @unom/* von dieser Registry bezogen werden sollen – alles andere läuft weiterhin über das Standard-Registry.
Schritt 2: Paket installieren
# Bun
bun add -D @unom/vite-sitemap-plugin
# npm
npm install -D @unom/vite-sitemap-plugin
# pnpm
pnpm add -D @unom/vite-sitemap-plugin
Schritt 3: Plugin in vite.config.ts einbinden
Das Plugin wird wie jedes andere Vite-Plugin in der plugins-Array eingetragen:
import { defineConfig } from "vite";
import { sitemapPlugin } from "@unom/vite-sitemap-plugin";
export default defineConfig({
plugins: [
sitemapPlugin({
siteUrl: "https://example.com",
routes: [
"",
"/about",
"/contact",
{ path: "/blog", changefreq: "weekly", priority: 0.8 },
],
}),
],
});Nach dem nächsten Build (vite build) liegen sitemap.xml und robots.txt im Output-Verzeichnis (standardmäßig public/).
Schritt 4: robots.txt konfigurieren
Standardmäßig erzeugt das Plugin eine robots.txt ohne Einschränkungen. Über die robots-Option lässt sich das anpassen:
sitemapPlugin({
siteUrl: "https://example.com",
routes: ["", "/about"],
robots: {
userAgent: "*", // Standard: "*"
disallow: ["/admin/", "/api/"],
allow: ["/api/public/"],
extraLines: [
"Crawl-delay: 10",
],
},
})Die generierte robots.txt sieht dann so aus:
User-agent: *
Disallow: /admin/
Disallow: /api/
Allow: /api/public/
Crawl-delay: 10
Sitemap: https://example.com/sitemap.xmlWer gar keine robots.txt will, übergibt robots: false.
Schritt 5: Mehrsprachige Seiten mit hreflang
Für mehrsprachige Projekte ist das der Hauptgrund, warum dieses Plugin entstanden ist. Mit der locales-Option erzeugt das Plugin für jede Locale/Pfad-Kombination einen eigenen <url>-Eintrag inklusive aller <xhtml:link rel="alternate">-Verweise:
sitemapPlugin({
siteUrl: "https://example.com",
locales: ["de", "en", "fr"],
defaultLocale: "de",
routes: [
"",
"/ueber-uns",
"/kontakt",
{ path: "/blog", changefreq: "weekly", priority: 0.9 },
],
})Das erzeugt URLs im Format https://example.com/de/, https://example.com/en/, https://example.com/fr/ usw. Jeder <url>-Block enthält dann die vollständigen hreflang-Alternates plus ein x-default, das auf die defaultLocale zeigt.
Ausschnitt aus der resultierenden sitemap.xml:
<url>
<loc>https://example.com/de/</loc>
<lastmod>2026-05-12</lastmod>
<xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/"/>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/"/>
<xhtml:link rel="alternate" hreflang="fr" href="https://example.com/fr/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/de/"/>
</url>Schritt 6: URL-Format anpassen
Standardmäßig baut das Plugin URLs nach dem Schema ${siteUrl}/${locale}${path}. Wenn das eigene Routing anders aussieht – z.B. de.example.com statt example.com/de/ oder Query-Parameter statt Pfad-Segmenten – kann das mit formatUrlvollständig überschrieben werden:
sitemapPlugin({
siteUrl: "https://example.com",
locales: ["de", "en"],
defaultLocale: "de",
routes: ["", "/about"],
formatUrl: (locale, path) =>
locale === "de"
? `https://example.com${path}` // Deutsch ohne Locale-Prefix
: `https://${locale}.example.com${path}`, // Subdomains für andere Locales
})Alle Optionen im Überblick
OptionTypStandardBeschreibung
siteUrl
string
(Pflicht)
Basis-URL der Website
routes
Array<string | SitemapEntry>
(Pflicht)
Liste der zu indexierenden Pfade
locales
string[]
nicht gesetzt
Locale-Codes für i18n hreflang
defaultLocale
string
erste Locale
Locale für x-default hreflang
formatUrl
(locale, path) => string
${siteUrl}/${locale}${path}
Benutzerdefinierte URL-Formatierung
lastmod
string | Date
Build-Zeitpunkt
Globales lastmod für alle Einträge
outDir
string
"public"
Ausgabeverzeichnis
sitemapFilename
string
"sitemap.xml"
Dateiname der Sitemap
robotsFilename
string
"robots.txt"
Dateiname der robots.txt
robots
RobotsOptions | false
{}
robots.txt-Konfiguration
SitemapEntry akzeptiert: path, lastmod, changefreq, priority.
RobotsOptions akzeptiert: userAgent, disallow, allow, extraLines.
Vollständiges Beispiel
Ein realistisches Setup für eine deutschsprachige Hauptseite mit englischer Übersetzung, einem Blog und einem geschützten Admin-Bereich:
import { defineConfig } from "vite";
import { sitemapPlugin } from "@unom/vite-sitemap-plugin";
export default defineConfig({
plugins: [
sitemapPlugin({
siteUrl: "https://meineprojekt.de",
locales: ["de", "en"],
defaultLocale: "de",
routes: [
{ path: "", changefreq: "daily", priority: 1.0 },
{ path: "/about", changefreq: "monthly", priority: 0.5 },
{ path: "/blog", changefreq: "weekly", priority: 0.8 },
{ path: "/contact",changefreq: "monthly", priority: 0.6 },
],
robots: {
disallow: ["/admin/", "/api/internal/"],
},
}),
],
});Warum nicht einfach vite-plugin-sitemap?
Es gibt bereits einige Sitemap-Plugins für Vite. Die Probleme, auf die ich gestoßen bin:
- Kein oder mangelhaftes hreflang-Handling für mehrsprachige Projekte
- Keine Kontrolle über
robots.txtim selben Plugin - TanStack Starts eigene Sitemap-Unterstützung ist noch sehr früh – keine Konfigurationsmöglichkeiten, kein i18n
- Ich wollte ein Plugin, das ich vollständig verstehe und kontrolliere, ohne Runtime-Overhead
Das Ergebnis ist ein Plugin mit ~200 Zeilen TypeScript, keinen externen Abhängigkeiten, und genau den Features, die für meine Projekte gebraucht werden. MIT-lizenziert, Quellen offen.
Links
- Repository: git.unom.io/unom/vite-sitemap-plugin
- Registry:
https://git.unom.io/api/packages/unom/npm/ - Paketname:
@unom/vite-sitemap-plugin
