Structured data is how you get review stars, FAQ dropdowns, breadcrumbs and article cards in search results — and it's also the format AI engines parse most reliably. In the App Router, render JSON-LD as a script tag from a Server Component (typed with schema-dts):
import type { WithContext, BlogPosting } from "schema-dts";
export default async function PostPage({ params }) {
const post = await getPost(params.slug);
const jsonLd: WithContext<BlogPosting> = {
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: post.title,
description: post.excerpt,
datePublished: post.publishedAt,
dateModified: post.updatedAt,
author: {
"@type": "Person",
name: "Vlad Sedenko",
url: "https://example.com/about",
},
image: post.ogImage,
};
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<article>{/* ... */}</article>
</>
);
}
Which types to implement, in order of impact:
Organization/Person— site-wide, in the root layout. Feeds the knowledge panel.BlogPosting/Article— every post.BreadcrumbList— replaces the URL with a readable path in results.FAQPage— for pages with genuine Q&A content (also heavily quoted by AI engines).Product+Offer+AggregateRating— e-commerce; this is where the stars come from.Service+LocalBusiness— agencies and local companies.
Validate with Google's Rich Results Test — the schema.org validator checks syntax, but only Google's tool tells you whether you're eligible for rich results.
Checklist:
- JSON-LD rendered server-side (never injected on the client after load)
- Article/BlogPosting on all posts with real dates
- BreadcrumbList site-wide
- Data in the markup matches the visible page (mismatches can earn a manual action)