
JSON-LD Schema für SPAs automatisieren: Structured Data ohne Backend
TL;DR: „JSON-LD Schema macht deine SPA für Google's Rich Results sichtbar. Mit React-Komponenten kannst du Organization, BlogPosting und BreadcrumbList automatisch auf jeder Seite generieren – ohne Backend, ohne CMS."
— Till FreitagWarum SPAs kein Structured Data haben
Single Page Applications haben ein doppeltes SEO-Problem:
- Rendering: Der HTML-Body ist initial leer (gelöst durch Playwright SSG)
- Structured Data: Es gibt kein Backend, das JSON-LD automatisch in den HTML-Head injiziert
Ohne Structured Data verpasst du:
- Rich Snippets in Google-Suchergebnissen (Sternebewertungen, FAQ-Dropdowns, Breadcrumbs)
- Knowledge Graph Einträge für dein Unternehmen
- Event-Listings in der Google-Suche
- Artikel-Karussells für Blog-Content
Kontext: Dieser Artikel ist Teil unseres Vibe Coding SEO Playbooks. Dort findest du den Gesamtüberblick.
JSON-LD Grundlagen
JSON-LD (JavaScript Object Notation for Linked Data) ist Googles bevorzugtes Format für Structured Data. Es wird als <script type="application/ld+json"> im HTML platziert und beschreibt den Inhalt der Seite maschinenlesbar.
Beispiel: Organization Schema
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Till Freitag",
"url": "https://till-freitag.com",
"logo": "https://till-freitag.com/logo.png",
"sameAs": [
"https://linkedin.com/company/tillfreitag",
"https://github.com/tillfreitag"
]
}Dieses Schema sagt Google: „Hier ist ein Unternehmen namens Till Freitag, das unter dieser URL erreichbar ist."
Die React-Komponente: SEOHead
Anstatt JSON-LD manuell in jede Seite zu kopieren, bauen wir eine wiederverwendbare React-Komponente, die das Schema automatisch generiert:
// components/seo/SEOHead.tsx
import { Helmet } from 'react-helmet-async';
interface SEOHeadProps {
title: string;
description: string;
canonicalUrl: string;
jsonLd?: Record<string, unknown>[];
ogImage?: string;
ogType?: string;
}
export default function SEOHead({
title,
description,
canonicalUrl,
jsonLd = [],
ogImage,
ogType = 'website',
}: SEOHeadProps) {
return (
<Helmet>
<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonicalUrl} />
{/* Open Graph */}
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:type" content={ogType} />
<meta property="og:url" content={canonicalUrl} />
{ogImage && <meta property="og:image" content={ogImage} />}
{/* JSON-LD Schemas */}
{jsonLd.map((schema, i) => (
<script key={i} type="application/ld+json">
{JSON.stringify(schema)}
</script>
))}
</Helmet>
);
}Wichtig: Die Komponente akzeptiert ein Array von Schemas. So kannst du pro Seite mehrere Schema-Typen kombinieren.
Schema-Typen für typische Seiten
1. BlogPosting (für Artikel)
function generateBlogPostingSchema(post: BlogPost) {
return {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.title,
description: post.excerpt,
url: `https://deine-domain.com/blog/${post.slug}`,
datePublished: post.date,
dateModified: post.modified || post.date,
wordCount: post.content.split(/\s+/).length,
author: {
'@type': 'Person',
name: post.author.name,
url: `https://deine-domain.com/blog/autor/${post.author.slug}`,
},
publisher: {
'@type': 'Organization',
name: 'Dein Unternehmen',
logo: {
'@type': 'ImageObject',
url: 'https://deine-domain.com/logo.png',
},
},
mainEntityOfPage: {
'@type': 'WebPage',
'@id': `https://deine-domain.com/blog/${post.slug}`,
},
};
}2. BreadcrumbList (für Navigation)
function generateBreadcrumbSchema(
items: { name: string; path: string }[]
) {
return {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: items.map((item, index) => ({
'@type': 'ListItem',
position: index + 1,
item: {
'@type': 'WebPage',
'@id': `https://deine-domain.com${item.path}`,
name: item.name,
},
})),
};
}⚠️ Häufiger Fehler: Das
itemFeld muss ein strukturiertes Objekt mit@idsein – nicht nur ein String. Google's Rich Results Test wird sonst Fehler melden.
3. ProfessionalService (für Dienstleistungsseiten)
function generateServiceSchema(service: Service) {
return {
'@context': 'https://schema.org',
'@type': 'ProfessionalService',
name: service.name,
description: service.description,
url: `https://deine-domain.com${service.path}`,
provider: {
'@type': 'Organization',
name: 'Dein Unternehmen',
},
areaServed: [
{ '@type': 'Country', name: 'Germany' },
{ '@type': 'Country', name: 'Austria' },
{ '@type': 'Country', name: 'Switzerland' },
],
serviceType: service.type,
};
}4. FAQPage (für FAQ-Sektionen)
function generateFAQSchema(
faqs: { question: string; answer: string }[]
) {
return {
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: faqs.map(faq => ({
'@type': 'Question',
name: faq.question,
acceptedAnswer: {
'@type': 'Answer',
text: faq.answer,
},
})),
};
}Automatische Schema-Generierung pro Route
Der eleganteste Ansatz: Definiere Schema-Regeln pro Route-Typ und lass die Komponente das richtige Schema automatisch wählen:
// lib/schema-config.ts
type SchemaRule = {
pattern: RegExp;
generate: (params: RouteParams) => Record<string, unknown>[];
};
const schemaRules: SchemaRule[] = [
{
pattern: /^\/blog\/[^/]+$/,
generate: (params) => [
generateBlogPostingSchema(params.post),
generateBreadcrumbSchema([
{ name: 'Home', path: '/' },
{ name: 'Blog', path: '/blog' },
{ name: params.post.title, path: params.path },
]),
],
},
{
pattern: /^\/services\//,
generate: (params) => [
generateServiceSchema(params.service),
generateBreadcrumbSchema([
{ name: 'Home', path: '/' },
{ name: 'Services', path: '/services' },
{ name: params.service.name, path: params.path },
]),
],
},
{
pattern: /^\/$/, // Homepage
generate: () => [
generateOrganizationSchema(),
generateWebSiteSchema(),
],
},
];Häufige Schema-Fehler
1. Fehlendes item in BreadcrumbList
❌ Falsch:
{
"@type": "ListItem",
"position": 1,
"name": "Blog",
"item": "/blog"
}✅ Richtig:
{
"@type": "ListItem",
"position": 1,
"item": {
"@type": "WebPage",
"@id": "https://deine-domain.com/blog",
"name": "Blog"
}
}2. Fehlendes offers in Event-Schemas
Auch bei kostenlosen Events erwartet Google ein offers-Feld:
{
"@type": "Event",
"offers": {
"@type": "Offer",
"availability": "https://schema.org/SoldOut",
"price": "0",
"priceCurrency": "EUR"
}
}3. Relative URLs statt absolute URLs
❌ "url": "/blog/mein-artikel"
✅ "url": "https://deine-domain.com/blog/mein-artikel"
Google benötigt immer absolute URLs in JSON-LD.
Validierung
Nach der Implementierung:
- Google Rich Results Test: search.google.com/test/rich-results
- Schema.org Validator: validator.schema.org
- Browser DevTools:
document.querySelectorAll('script[type="application/ld+json"]')→ Prüfe den Output
Pro-Tipp: Baue einen automatisierten Test, der alle Routen auf gültiges JSON-LD prüft:
// tests/schema.test.ts
import { test, expect } from '@playwright/test';
test('all pages have valid JSON-LD', async ({ page }) => {
const routes = ['/', '/blog', '/services/vibe-coding-seo'];
for (const route of routes) {
await page.goto(`http://localhost:4173${route}`);
const schemas = await page.$$eval(
'script[type="application/ld+json"]',
scripts => scripts.map(s => JSON.parse(s.textContent || '{}'))
);
expect(schemas.length).toBeGreaterThan(0);
schemas.forEach(schema => {
expect(schema['@context']).toBe('https://schema.org');
expect(schema['@type']).toBeTruthy();
});
}
});Fazit: Schema macht deine SPA smart
JSON-LD Schema ist die zweite Säule nach Playwright SSG für vollständige SPA-SEO. Zusammen sorgen sie dafür, dass Google deine Inhalte nicht nur sehen, sondern auch verstehen kann.
Der komplette Vibe Coding SEO Stack:
- Lovable → Generiert die App
- Playwright SSG → Rendert statisches HTML
- JSON-LD Schema → Strukturierte Daten (dieser Artikel)
- Vercel Edge → Schnelle Auslieferung
Klingt komplex? Wir setzen das für dich um →
In unserem SEO-Audit prüfen wir dein Schema, dein Rendering und deine Core Web Vitals – und liefern einen konkreten Implementierungsplan.
Verwandte Artikel
- 📸 OG-Image Best Practices für SPAs – Social Previews sind die sichtbarste Form von Structured Data. So machst du sie richtig.
- 📊 Google Search Console für Vibe-Coding-Projekte – Prüfe ob dein Schema-Markup von Google erkannt wird – mit der URL-Inspektion und dem Rich Results Test.








