Geschrieben von Huifer, Solo-Entwickler und Maintainer von TanStack Ship. Ich habe drei SaaS-Produkte mit TanStack-Bibliotheken entwickelt, und jedes einzelne hatte beim Launch dasselbe SEO-Problem: Google indexierte das JavaScript, rendert nichts und zeigte die falsche Meta-Beschreibung in den Suchergebnissen. Die Lösung war stets SSR – Server-Side Rendering. Aber SSR mit TanStack-Bibliotheken in einem benutzerdefinierten Vite-Setup einzurichten, ist schmerzhaft. TanStack Start löst dieses Problem, indem es SSR zum Standard macht – nicht zur Nachdenksache. Dieser Guide deckt ab, was ich beim Sichtbarmachen von TanStack-Apps für Google gelernt habe.
Verifizierte Quellen: TanStack Start SSR · Google SSR SEO-Dokumentation · Core Web Vitals · Strukturierte Daten Letztes Update: 2026-09-18 · Changelog
TL;DR: Drei Dinge machen eine TanStack-App rankfähig: SSR (damit Google den gerenderten HTML-Code sieht), korrekte Meta Tags pro Route und Core Web Vitals, die bestehen. TanStack Start handhabt alle drei nativ. Das ist der vollständige Implementierungsguide.
Das SSR-Problem mit TanStack-Apps
Single-Page-Apps, die mit TanStack Router, TanStack Query und React gebaut wurden, haben ein Rendering-Problem: Googlebot indexiert das JavaScript, wartet aber nicht auf dessen Ausführung. Das Ergebnis: Google sieht eine leere Seite oder den falschen Inhalt.
Die Lösung ist Server-Side Rendering. Mit SSR führt der Server den React-Komponentenbaum aus, erzeugt HTML und sendet dieses HTML an Google. Das JavaScript wird danach auf dem Client ausgeführt – die sogenannte „Hydration"-Phase.
Für TanStack-Apps gibt es zwei Wege:
- TanStack Start: SSR ist integriert. Das Framework handhabt Hydration, Meta Tags und Caching automatisch.
- Benutzerdefiniertes Vite + React-Setup: SSR erfordert eine manuelle Konfiguration mit
@vitejs/plugin-reactund einem Node.js-Server.
TanStack Start ist die richtige Wahl für neue Projekte. Dieser Guide deckt beide Wege ab.
TanStack Start: SSR von Haus aus
TanStack Start bietet erstklassige SSR-Unterstützung. Routes werden auf dem Server gerendert und senden HTML an den Client:
// src/routes/posts.$postId.tsx
import { createFileRoute, Link } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/start/server'
const fetchPost = createServerFn({ method: 'GET' })
.validator(z.object({ postId: z.string() }))
.handler(async ({ data }) => {
const post = await db.post.findUnique({ where: { id: data.postId } })
if (!post) throw new Response('Not Found', { status: 404 })
return post
})
export const Route = createFileRoute('/posts/$postId')({
validateSearch: z.object({
ref: z.string().optional(),
}),
loader: async ({ params, deps }) => {
const post = await deps.posts.findUnique(params.postId)
return { post }
},
meta: ({ params, deps }) => [
{ title: `${deps.post?.title} | My App` },
{ name: 'description', content: deps.post?.description },
{ property: 'og:title', content: deps.post?.title },
{ property: 'og:image', content: deps.post?.ogImage },
],
component: PostPage,
})
Die meta-Funktion setzt Meta Tags pro Route. Google liest diese aus dem server-gerenderten HTML – ohne JavaScript-Ausführung.
Meta Tags: Das Per-Route-Muster
In TanStack Start werden Meta Tags pro Route über die meta-Funktion gesetzt. Die Tags werden server-seitig gerendert und in den HTML-<head> eingefügt:
export const Route = createFileRoute('/products/$productId')({
loader: async ({ params }) => {
const product = await getProduct(params.productId)
return { product }
},
meta: ({ data }) => [
{ title: `${data.product.name} — Buyer's Guide` },
{ name: 'description', content: truncate(data.product.description, 160) },
{ property: 'og:title', content: data.product.name },
{ property: 'og:description', content: data.product.description },
{ property: 'og:image', content: data.product.heroImage },
{ property: 'og:type', content: 'product' },
{ name: 'twitter:card', content: 'summary_large_image' },
// Strukturierte Daten
{ tagName: 'script', type: 'application/ld+json', children: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Product',
name: data.product.name,
description: data.product.description,
image: data.product.heroImage,
offers: {
'@type': 'Offer',
price: data.product.price,
priceCurrency: 'USD',
},
})},
],
component: ProductPage,
})
Die JSON-LD-Strukturdaten machen das Produkt für Googles Product Rich Results berechtigt – die Sterne und der Preis im Such-Snippet.
Sitemap-Generierung
TanStack Start generiert Sitemaps automatisch:
// src/routes/sitemap.xml.ts
import { createFileRoute } from '@tanstack/react-router'
import { db } from '@/lib/db'
export const Route = createFileRoute('/sitemap.xml')({
loader: async () => {
const posts = await db.post.findMany({
where: { status: 'published' },
select: { slug: true, updatedAt: true },
})
const staticRoutes = [
'/',
'/pricing',
'/docs',
'/blog',
]
const dynamicRoutes = posts.map(post => `/blog/${post.slug}`)
const allRoutes = [...staticRoutes, ...dynamicRoutes]
return new Response(
`<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${allRoutes
.map(
route => ` <url>
<loc>https://yourdomain.com${route}</loc>
<lastmod>${new Date().toISOString().split('T')[0]}</lastmod>
<changefreq>${route === '/' ? 'daily' : 'weekly'}</changefreq>
<priority>${route === '/' ? '1.0' : route.startsWith('/blog') ? '0.8' : '0.6'}</priority>
</url>`
)
.join('\n')}
</urlset>`,
{
headers: { 'Content-Type': 'application/xml' },
}
)
},
})
Über den Sitemaps-Bericht in der Google Search Console einreichen.
Core Web Vitals für TanStack-Apps
LCP (Largest Contentful Paint)
LCP ist das häufigste Vital-Problem bei TanStack-SPAs. Die Lösung: das Hero-Bild optimieren und sicherstellen, dass SSR bedeutsamen Inhalt liefert.
Bei TanStack Start mit SSR enthält das initiale HTML den server-gerenderten Inhalt. LCP misst das größte Bild oder Textblock im Viewport. Stellen Sie sicher, dass das Hero-Bild folgendes hat:
<!-- Das Hero-Bild vorladen, damit LCP schnell ist -->
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high" />
Setzen Sie außerdem fetchpriority="high" auf dem LCP-Bildelement:
<img
src="/hero.webp"
alt="Hero image"
fetchpriority="high"
loading="eager"
/>
INP (Interaction to Next Paint)
INP misst die Reaktionsfähigkeit auf Benutzerinteraktionen. TanStack Query-Mutationen, die die UI aktualisieren, können INP-Regressionen verursachen, wenn sie zu viel neu rendern.
Die Lösung: Mutationen auf die Daten beschränken, die sie aktualisieren, und optimistic Updates verwenden, damit Interaktionen sofort wirken:
const mutation = useMutation({
mutationFn: updatePost,
onMutate: async (newData) => {
await queryClient.cancelQueries({ queryKey: ['post', newData.id] })
const previous = queryClient.getQueryData(['post', newData.id])
queryClient.setQueryData(['post', newData.id], newData)
return { previous }
},
onError: (err, newData, context) => {
queryClient.setQueryData(['post', newData.id], context.previous)
},
})
CLS (Cumulative Layout Shift)
CLS tritt auf, wenn Inhalt nach dem initialen Rendern geladen wird und das Layout verschiebt. TanStack Querys placeholderData ist eine häufige CLS-Quelle – es rendert einen leeren Zustand, der sich verschiebt, wenn Daten eintreffen.
Beheben: placeholderData mit derselben Struktur wie reale Daten verwenden und explizite Dimensionen für Inhaltsbereiche setzen:
const { data } = useQuery({
queryKey: ['post', postId],
queryFn: () => fetchPost(postId),
placeholderData: keepPreviousData, // Beibehaltung der vorherigen Struktur – kein CLS
})
Weiterführende Lektüre: Tanstack Query V5 Guide 2026 · Tanstack Query Best Practices Guide · Tanstack Pacer Complete Guide 2026
Die vollständige TanStack-SEO-Checklist
- SSR aktiviert — TanStack Start SSR oder benutzerdefiniertes SSR-Setup mit Vite
- Meta Tags pro Route — title, description, og:title, og:description für jede Route
- Kanonische URLs — jede Seite hat ein
<link rel="canonical"> - Sitemap generiert —
/sitemap.xmlenthält alle öffentlichen Routen - Strukturierte Daten — JSON-LD für Produkte, Artikel, FAQs nach Bedarf
- Hero-Bild vorgeladen —
<link rel="preload">mitfetchpriority="high" - LCP < 2,5s — mit PageSpeed Insights testen
- INP < 200ms — optimistic Updates verwenden, große Re-Renders vermeiden
- CLS < 0,1 —
keepPreviousDataverwenden, explizite Dimensionen setzen - Google Search Console verifiziert — Sitemap einreichen, Abdeckung überwachen
TanStack Ship wird mit vorkonfiguriertem TanStack Start SSR, Meta Tags, Sitemap-Generierung und Core Web Vitals-Optimierung ausgeliefert. Mehr dazu im TanStack Ship Deployment Guide und in der vollständigen Feature-Liste.