2 min read
SEOStructured Data & JSON-LD
Strukturierte Daten mit JSON-LD für SEO. Schema.org Typen, Rich Snippets und Google Rich Results für bessere Sichtbarkeit.
JSON-LDStructured DataSchema.orgRich SnippetsSEOGoogle Rich Results

Structured Data & JSON-LD
Meta-Description: Strukturierte Daten mit JSON-LD für SEO. Schema.org Typen, Rich Snippets und Google Rich Results für bessere Sichtbarkeit.
Keywords: JSON-LD, Structured Data, Schema.org, Rich Snippets, SEO, Google Rich Results, Semantic Web
Einführung
JSON-LD (JavaScript Object Notation for Linked Data) ist Googles bevorzugtes Format für strukturierte Daten. Es ermöglicht Rich Snippets in Suchergebnissen – von Sternebewertungen bis FAQs. Dieser Guide zeigt die wichtigsten Schema-Typen für moderne Websites.
JSON-LD Overview
┌─────────────────────────────────────────────────────────────┐
│ STRUCTURED DATA ECOSYSTEM │
├─────────────────────────────────────────────────────────────┤
│ │
│ Schema.org Hierarchy: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Thing (Base Type) │ │
│ │ ├── CreativeWork │ │
│ │ │ ├── Article │ │
│ │ │ ├── BlogPosting │ │
│ │ │ ├── WebPage │ │
│ │ │ └── SoftwareApplication │ │
│ │ ├── Organization │ │
│ │ │ ├── LocalBusiness │ │
│ │ │ └── Corporation │ │
│ │ ├── Person │ │
│ │ ├── Product │ │
│ │ ├── Event │ │
│ │ └── Place │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Rich Results Types: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ⭐ Review Snippets (Sternebewertungen) │ │
│ │ ❓ FAQ Snippets (Accordion Q&A) │ │
│ │ 📝 How-To Snippets (Schritt-für-Schritt) │ │
│ │ 🛒 Product Snippets (Preis, Verfügbarkeit) │ │
│ │ 📅 Event Snippets (Datum, Ort) │ │
│ │ 👤 Person Knowledge Panel │ │
│ │ 🏢 Organization Knowledge Panel │ │
│ │ 🍳 Recipe Cards (Kochzeit, Kalorien) │ │
│ │ 💼 Job Postings (Gehalt, Standort) │ │
│ │ 🔍 Sitelinks Searchbox │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Implementation: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ <script type="application/ld+json"> │ │
│ │ { │ │
│ │ "@context": "https://schema.org", │ │
│ │ "@type": "Article", │ │
│ │ "headline": "...", │ │
│ │ "author": { "@type": "Person", ... } │ │
│ │ } │ │
│ │ </script> │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘TypeScript JSON-LD Library
// lib/jsonld/types.ts - Type Definitions
export interface Thing {
'@context': 'https://schema.org';
'@type': string;
name?: string;
description?: string;
url?: string;
image?: string | ImageObject;
}
export interface ImageObject {
'@type': 'ImageObject';
url: string;
width?: number;
height?: number;
caption?: string;
}
export interface Person {
'@type': 'Person';
name: string;
url?: string;
image?: string;
jobTitle?: string;
worksFor?: Organization;
sameAs?: string[];
}
export interface Organization {
'@type': 'Organization' | 'LocalBusiness' | 'Corporation';
name: string;
url?: string;
logo?: string | ImageObject;
sameAs?: string[];
contactPoint?: ContactPoint[];
address?: PostalAddress;
}
export interface ContactPoint {
'@type': 'ContactPoint';
telephone: string;
contactType: string;
availableLanguage?: string[];
}
export interface PostalAddress {
'@type': 'PostalAddress';
streetAddress?: string;
addressLocality?: string;
addressRegion?: string;
postalCode?: string;
addressCountry?: string;
}
export interface Article {
'@context': 'https://schema.org';
'@type': 'Article' | 'BlogPosting' | 'NewsArticle' | 'TechArticle';
headline: string;
description?: string;
image?: string | string[];
datePublished: string;
dateModified?: string;
author: Person | Person[];
publisher: Organization;
mainEntityOfPage?: WebPage;
articleBody?: string;
wordCount?: number;
keywords?: string[];
}
export interface WebPage {
'@type': 'WebPage';
'@id': string;
}
export interface Product {
'@context': 'https://schema.org';
'@type': 'Product';
name: string;
description?: string;
image?: string | string[];
brand?: Brand;
offers?: Offer | Offer[];
aggregateRating?: AggregateRating;
review?: Review[];
sku?: string;
gtin13?: string;
}
export interface Brand {
'@type': 'Brand';
name: string;
}
export interface Offer {
'@type': 'Offer';
price: number;
priceCurrency: string;
availability: 'https://schema.org/InStock' | 'https://schema.org/OutOfStock' | 'https://schema.org/PreOrder';
url?: string;
priceValidUntil?: string;
seller?: Organization;
}
export interface AggregateRating {
'@type': 'AggregateRating';
ratingValue: number;
reviewCount: number;
bestRating?: number;
worstRating?: number;
}
export interface Review {
'@type': 'Review';
author: Person;
datePublished: string;
reviewBody?: string;
reviewRating: Rating;
}
export interface Rating {
'@type': 'Rating';
ratingValue: number;
bestRating?: number;
worstRating?: number;
}
export interface FAQPage {
'@context': 'https://schema.org';
'@type': 'FAQPage';
mainEntity: Question[];
}
export interface Question {
'@type': 'Question';
name: string;
acceptedAnswer: Answer;
}
export interface Answer {
'@type': 'Answer';
text: string;
}
export interface BreadcrumbList {
'@context': 'https://schema.org';
'@type': 'BreadcrumbList';
itemListElement: ListItem[];
}
export interface ListItem {
'@type': 'ListItem';
position: number;
name: string;
item?: string;
}
export interface HowTo {
'@context': 'https://schema.org';
'@type': 'HowTo';
name: string;
description?: string;
image?: string;
totalTime?: string; // ISO 8601 duration
estimatedCost?: MonetaryAmount;
supply?: HowToSupply[];
tool?: HowToTool[];
step: HowToStep[];
}
export interface HowToStep {
'@type': 'HowToStep';
name: string;
text: string;
image?: string;
url?: string;
}
export interface HowToSupply {
'@type': 'HowToSupply';
name: string;
}
export interface HowToTool {
'@type': 'HowToTool';
name: string;
}
export interface MonetaryAmount {
'@type': 'MonetaryAmount';
currency: string;
value: number;
}
export interface Event {
'@context': 'https://schema.org';
'@type': 'Event' | 'BusinessEvent' | 'MusicEvent' | 'SportsEvent';
name: string;
description?: string;
startDate: string;
endDate?: string;
location: Place | VirtualLocation;
organizer?: Organization | Person;
performer?: Person | Organization;
offers?: Offer[];
eventStatus?: 'https://schema.org/EventScheduled' | 'https://schema.org/EventCancelled' | 'https://schema.org/EventPostponed';
eventAttendanceMode?: 'https://schema.org/OnlineEventAttendanceMode' | 'https://schema.org/OfflineEventAttendanceMode' | 'https://schema.org/MixedEventAttendanceMode';
}
export interface Place {
'@type': 'Place';
name: string;
address: PostalAddress;
}
export interface VirtualLocation {
'@type': 'VirtualLocation';
url: string;
}JSON-LD Components
// components/jsonld/JsonLd.tsx - Base Component
import Script from 'next/script';
interface JsonLdProps<T> {
data: T;
id?: string;
}
export function JsonLd<T extends object>({ data, id }: JsonLdProps<T>) {
return (
<Script
id={id || 'json-ld'}
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(data, null, 0)
}}
strategy="beforeInteractive"
/>
);
}
// components/jsonld/ArticleJsonLd.tsx
import { JsonLd } from './JsonLd';
import type { Article, Person, Organization } from '@/lib/jsonld/types';
interface ArticleJsonLdProps {
title: string;
description: string;
publishedTime: string;
modifiedTime?: string;
authorName: string;
authorUrl?: string;
images: string[];
url: string;
siteName: string;
logoUrl: string;
}
export function ArticleJsonLd({
title,
description,
publishedTime,
modifiedTime,
authorName,
authorUrl,
images,
url,
siteName,
logoUrl
}: ArticleJsonLdProps) {
const author: Person = {
'@type': 'Person',
name: authorName,
...(authorUrl && { url: authorUrl })
};
const publisher: Organization = {
'@type': 'Organization',
name: siteName,
logo: {
'@type': 'ImageObject',
url: logoUrl,
width: 600,
height: 60
}
};
const data: Article = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: title,
description,
image: images,
datePublished: publishedTime,
...(modifiedTime && { dateModified: modifiedTime }),
author,
publisher,
mainEntityOfPage: {
'@type': 'WebPage',
'@id': url
}
};
return <JsonLd data={data} id="article-jsonld" />;
}
// components/jsonld/ProductJsonLd.tsx
import { JsonLd } from './JsonLd';
import type { Product, Offer, AggregateRating } from '@/lib/jsonld/types';
interface ProductJsonLdProps {
name: string;
description: string;
images: string[];
brand: string;
sku: string;
price: number;
currency: string;
availability: 'InStock' | 'OutOfStock' | 'PreOrder';
ratingValue?: number;
reviewCount?: number;
url: string;
}
export function ProductJsonLd({
name,
description,
images,
brand,
sku,
price,
currency,
availability,
ratingValue,
reviewCount,
url
}: ProductJsonLdProps) {
const offer: Offer = {
'@type': 'Offer',
price,
priceCurrency: currency,
availability: `https://schema.org/${availability}`,
url
};
const data: Product = {
'@context': 'https://schema.org',
'@type': 'Product',
name,
description,
image: images,
brand: {
'@type': 'Brand',
name: brand
},
sku,
offers: offer,
...(ratingValue && reviewCount && {
aggregateRating: {
'@type': 'AggregateRating',
ratingValue,
reviewCount,
bestRating: 5,
worstRating: 1
} as AggregateRating
})
};
return <JsonLd data={data} id="product-jsonld" />;
}// components/jsonld/FAQJsonLd.tsx
import { JsonLd } from './JsonLd';
import type { FAQPage, Question } from '@/lib/jsonld/types';
interface FAQItem {
question: string;
answer: string;
}
interface FAQJsonLdProps {
items: FAQItem[];
}
export function FAQJsonLd({ items }: FAQJsonLdProps) {
const questions: Question[] = items.map(item => ({
'@type': 'Question',
name: item.question,
acceptedAnswer: {
'@type': 'Answer',
text: item.answer
}
}));
const data: FAQPage = {
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: questions
};
return <JsonLd data={data} id="faq-jsonld" />;
}
// components/jsonld/BreadcrumbJsonLd.tsx
import { JsonLd } from './JsonLd';
import type { BreadcrumbList, ListItem } from '@/lib/jsonld/types';
interface BreadcrumbItem {
name: string;
url?: string;
}
interface BreadcrumbJsonLdProps {
items: BreadcrumbItem[];
}
export function BreadcrumbJsonLd({ items }: BreadcrumbJsonLdProps) {
const listItems: ListItem[] = items.map((item, index) => ({
'@type': 'ListItem',
position: index + 1,
name: item.name,
...(item.url && { item: item.url })
}));
const data: BreadcrumbList = {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: listItems
};
return <JsonLd data={data} id="breadcrumb-jsonld" />;
}
// components/jsonld/OrganizationJsonLd.tsx
import { JsonLd } from './JsonLd';
import type { Organization, ContactPoint, PostalAddress } from '@/lib/jsonld/types';
interface OrganizationJsonLdProps {
name: string;
url: string;
logo: string;
description?: string;
email?: string;
telephone?: string;
address?: {
street: string;
city: string;
region: string;
postalCode: string;
country: string;
};
socialProfiles?: string[];
}
export function OrganizationJsonLd({
name,
url,
logo,
description,
email,
telephone,
address,
socialProfiles
}: OrganizationJsonLdProps) {
const contactPoints: ContactPoint[] = [];
if (telephone) {
contactPoints.push({
'@type': 'ContactPoint',
telephone,
contactType: 'customer service',
availableLanguage: ['German', 'English']
});
}
const postalAddress: PostalAddress | undefined = address ? {
'@type': 'PostalAddress',
streetAddress: address.street,
addressLocality: address.city,
addressRegion: address.region,
postalCode: address.postalCode,
addressCountry: address.country
} : undefined;
const data: Organization & { '@context': string; description?: string } = {
'@context': 'https://schema.org',
'@type': 'Organization',
name,
url,
logo: {
'@type': 'ImageObject',
url: logo,
width: 600,
height: 60
},
...(description && { description }),
...(contactPoints.length > 0 && { contactPoint: contactPoints }),
...(postalAddress && { address: postalAddress }),
...(socialProfiles && { sameAs: socialProfiles })
};
return <JsonLd data={data} id="organization-jsonld" />;
}Practical Examples
// app/blog/[slug]/page.tsx - Blog Article with JSON-LD
import { ArticleJsonLd } from '@/components/jsonld/ArticleJsonLd';
import { BreadcrumbJsonLd } from '@/components/jsonld/BreadcrumbJsonLd';
import { getBlogPost } from '@/lib/blog';
export default async function BlogPost({
params
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params;
const post = await getBlogPost(slug);
const baseUrl = 'https://example.com';
return (
<>
<ArticleJsonLd
title={post.title}
description={post.excerpt}
publishedTime={post.publishedAt}
modifiedTime={post.updatedAt}
authorName={post.author.name}
authorUrl={`${baseUrl}/author/${post.author.slug}`}
images={[post.coverImage]}
url={`${baseUrl}/blog/${slug}`}
siteName="Meine Website"
logoUrl={`${baseUrl}/logo.png`}
/>
<BreadcrumbJsonLd
items={[
{ name: 'Home', url: baseUrl },
{ name: 'Blog', url: `${baseUrl}/blog` },
{ name: post.title }
]}
/>
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
</>
);
}
// app/products/[slug]/page.tsx - Product with JSON-LD
import { ProductJsonLd } from '@/components/jsonld/ProductJsonLd';
import { BreadcrumbJsonLd } from '@/components/jsonld/BreadcrumbJsonLd';
import { FAQJsonLd } from '@/components/jsonld/FAQJsonLd';
import { getProduct } from '@/lib/products';
export default async function ProductPage({
params
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params;
const product = await getProduct(slug);
const baseUrl = 'https://example.com';
// Produkt-FAQs
const faqs = [
{
question: 'Wie lange ist die Lieferzeit?',
answer: 'Die Lieferzeit beträgt 2-3 Werktage innerhalb Deutschlands.'
},
{
question: 'Gibt es eine Garantie?',
answer: 'Ja, alle Produkte haben 2 Jahre Herstellergarantie.'
}
];
return (
<>
<ProductJsonLd
name={product.name}
description={product.description}
images={product.images}
brand={product.brand}
sku={product.sku}
price={product.price}
currency="EUR"
availability={product.inStock ? 'InStock' : 'OutOfStock'}
ratingValue={product.rating}
reviewCount={product.reviewCount}
url={`${baseUrl}/products/${slug}`}
/>
<BreadcrumbJsonLd
items={[
{ name: 'Home', url: baseUrl },
{ name: 'Produkte', url: `${baseUrl}/products` },
{ name: product.category, url: `${baseUrl}/products?category=${product.categorySlug}` },
{ name: product.name }
]}
/>
<FAQJsonLd items={faqs} />
<main>
<h1>{product.name}</h1>
{/* Product content */}
</main>
</>
);
}Advanced Schemas
// components/jsonld/HowToJsonLd.tsx
import { JsonLd } from './JsonLd';
import type { HowTo, HowToStep } from '@/lib/jsonld/types';
interface Step {
name: string;
text: string;
image?: string;
}
interface HowToJsonLdProps {
name: string;
description: string;
image?: string;
totalTime?: string; // e.g., "PT30M" for 30 minutes
steps: Step[];
}
export function HowToJsonLd({
name,
description,
image,
totalTime,
steps
}: HowToJsonLdProps) {
const howToSteps: HowToStep[] = steps.map((step, index) => ({
'@type': 'HowToStep',
name: step.name,
text: step.text,
...(step.image && { image: step.image }),
url: `#step-${index + 1}`
}));
const data: HowTo = {
'@context': 'https://schema.org',
'@type': 'HowTo',
name,
description,
...(image && { image }),
...(totalTime && { totalTime }),
step: howToSteps
};
return <JsonLd data={data} id="howto-jsonld" />;
}
// components/jsonld/EventJsonLd.tsx
import { JsonLd } from './JsonLd';
import type { Event, Place, VirtualLocation, Offer } from '@/lib/jsonld/types';
interface EventJsonLdProps {
name: string;
description: string;
startDate: string;
endDate?: string;
location: {
type: 'physical' | 'virtual' | 'hybrid';
name?: string;
address?: {
street: string;
city: string;
region: string;
postalCode: string;
country: string;
};
url?: string;
};
organizer: {
name: string;
url: string;
};
image?: string;
offers?: {
price: number;
currency: string;
url: string;
validFrom?: string;
};
status?: 'scheduled' | 'cancelled' | 'postponed';
}
export function EventJsonLd({
name,
description,
startDate,
endDate,
location,
organizer,
image,
offers,
status = 'scheduled'
}: EventJsonLdProps) {
const eventLocation = location.type === 'virtual' ? {
'@type': 'VirtualLocation' as const,
url: location.url!
} : {
'@type': 'Place' as const,
name: location.name!,
address: {
'@type': 'PostalAddress' as const,
streetAddress: location.address!.street,
addressLocality: location.address!.city,
addressRegion: location.address!.region,
postalCode: location.address!.postalCode,
addressCountry: location.address!.country
}
};
const attendanceMode = {
physical: 'https://schema.org/OfflineEventAttendanceMode',
virtual: 'https://schema.org/OnlineEventAttendanceMode',
hybrid: 'https://schema.org/MixedEventAttendanceMode'
}[location.type] as Event['eventAttendanceMode'];
const eventStatus = {
scheduled: 'https://schema.org/EventScheduled',
cancelled: 'https://schema.org/EventCancelled',
postponed: 'https://schema.org/EventPostponed'
}[status] as Event['eventStatus'];
const data: Event & { image?: string } = {
'@context': 'https://schema.org',
'@type': 'Event',
name,
description,
startDate,
...(endDate && { endDate }),
location: eventLocation,
organizer: {
'@type': 'Organization',
name: organizer.name,
url: organizer.url
},
eventAttendanceMode: attendanceMode,
eventStatus,
...(image && { image }),
...(offers && {
offers: {
'@type': 'Offer',
price: offers.price,
priceCurrency: offers.currency,
url: offers.url,
availability: 'https://schema.org/InStock',
...(offers.validFrom && { validFrom: offers.validFrom })
}
})
};
return <JsonLd data={data} id="event-jsonld" />;
}Validation & Testing
// lib/jsonld/validator.ts - Schema Validation
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
const ajv = new Ajv({ allErrors: true });
addFormats(ajv);
// Article Schema
const articleSchema = {
type: 'object',
required: ['@context', '@type', 'headline', 'datePublished', 'author', 'publisher'],
properties: {
'@context': { const: 'https://schema.org' },
'@type': { enum: ['Article', 'BlogPosting', 'NewsArticle', 'TechArticle'] },
headline: { type: 'string', maxLength: 110 },
description: { type: 'string', maxLength: 320 },
image: {
oneOf: [
{ type: 'string', format: 'uri' },
{ type: 'array', items: { type: 'string', format: 'uri' } }
]
},
datePublished: { type: 'string', format: 'date-time' },
dateModified: { type: 'string', format: 'date-time' },
author: {
type: 'object',
required: ['@type', 'name'],
properties: {
'@type': { const: 'Person' },
name: { type: 'string' },
url: { type: 'string', format: 'uri' }
}
},
publisher: {
type: 'object',
required: ['@type', 'name', 'logo'],
properties: {
'@type': { const: 'Organization' },
name: { type: 'string' },
logo: { type: 'object' }
}
}
}
};
export function validateArticleSchema(data: unknown): { valid: boolean; errors?: string[] } {
const validate = ajv.compile(articleSchema);
const valid = validate(data);
return {
valid,
errors: validate.errors?.map(e => `${e.instancePath} ${e.message}`)
};
}
// Test in Development
// app/api/validate-jsonld/route.ts
import { NextResponse } from 'next/server';
import { validateArticleSchema } from '@/lib/jsonld/validator';
export async function POST(request: Request) {
const data = await request.json();
const result = validateArticleSchema(data);
return NextResponse.json(result);
}Schema Type Reference
| Schema Type | Rich Result | Verwendung |
|---|---|---|
| **Article** | Article snippet | Blog, News |
| **Product** | Product snippet | E-Commerce |
| **FAQPage** | FAQ accordion | Support, Info |
| **HowTo** | Step-by-step | Tutorials |
| **Event** | Event listing | Veranstaltungen |
| **LocalBusiness** | Knowledge panel | Lokale Firmen |
| **BreadcrumbList** | Breadcrumb trail | Navigation |
| **Organization** | Knowledge panel | Unternehmen |
| **Person** | Knowledge panel | Personen |
| **Recipe** | Recipe card | Kochrezepte |
| **JobPosting** | Job listing | Stellenanzeigen |
| **Course** | Course info | Online-Kurse |
Fazit
Strukturierte Daten mit JSON-LD bieten:
- Rich Snippets: Bessere Sichtbarkeit in Suchergebnissen
- Type Safety: TypeScript Definitionen für Schema.org
- Reusable Components: Wiederverwendbare React Komponenten
- Validation: Schema-Validierung für Qualitätssicherung
Strukturierte Daten sind essentiell für moderne SEO.
Bildprompts
- "Google search results showing rich snippets, FAQ accordion, product ratings"
- "Schema.org hierarchy diagram, connected nodes visualization"
- "JSON-LD code in browser developer tools, structured data testing"