JSON-LD Structured Data in Nuxt
useSchemaOrg()provides type-safe JSON-LD with automatic graph linking- The Nuxt Schema.org module sets up WebSite and WebPage schemas automatically
- Rich results aren't guaranteed; validate your markup with Google's Rich Results Test
Schema.org structured data helps Google display Rich Results: star ratings, recipe cards, and product prices. Rotten Tomatoes added structured data to 100,000 pages and measured a 25% higher click-through rate compared to pages without it.
<!-- JSON-LD structured data in the head -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "JSON-LD Structured Data in Nuxt",
"author": { "@type": "Person", "name": "Your Name" }
}
</script>Google recommends JSON-LD as the easiest format to implement and maintain. Google supports rich results for many schema types including Article, Product, and Breadcrumb. See Rich Results for the full list of active types and eligibility details.
Rich results aren't guaranteed. Content must match the markup and follow Google's structured data guidelines.
Manual Implementation
Nuxt includes Unhead for head management. Add JSON-LD directly via useHead():
These blog examples cover the initial page load. Your /api/articles/:slug endpoint must return the typed fields shown below, or 404 for an absent article. Add route-change handling if the same page component stays mounted for another slug.
<script lang="ts" setup>
const route = useRoute()
const { data: article, error } = await useFetch<{
title: string
image: string
publishedAt: string
updatedAt: string
author: { name: string, url: string }
}>(`/api/articles/${encodeURIComponent(String(route.params.slug))}`)
if (error.value)
throw error.value
if (!article.value) {
throw createError({ statusCode: 404, statusMessage: 'Article not found' })
}
useHead({
script: [
{
type: 'application/ld+json',
innerHTML: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Article',
'headline': article.value.title,
'datePublished': article.value.publishedAt,
'author': {
'@type': 'Person',
'name': article.value.author.name
}
})
}
]
})
</script>For type safety and automatic graph linking, use useSchemaOrg() from @unhead/schema-org:
pnpm add -D @unhead/schema-orgSee Unhead Schema.org setup for configuration.
Automated Setup with Nuxt Schema.org
The Nuxt Schema.org module handles structured data automatically with zero config:
Or install the full Nuxt SEO module which includes Schema.org plus sitemaps, robots.txt, and OG images:
The module sets up base schema (WebSite, WebPage, Organization/Person) automatically and provides type-safe helpers for all common schema types.
useSchemaOrg([
defineArticle({
headline: 'JSON-LD Structured Data in Nuxt',
author: { name: 'Your Name' },
datePublished: new Date(2024, 0, 15),
})
])useHead({
script: [
{
type: 'application/ld+json',
innerHTML: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Article',
'headline': 'JSON-LD Structured Data in Nuxt',
'author': { '@type': 'Person', 'name': 'Your Name' }
})
}
],
})The defineX() helpers align with Google's Structured Data Guidelines and handle boilerplate:
| Helper | Rich Result Type |
|---|---|
defineArticle() | Article, NewsArticle, BlogPosting |
defineBreadcrumb() | Breadcrumb navigation |
defineQuestion() | FAQPage markup (no active rich result, see FAQ Schema) |
defineProduct() | Product listings |
Reactive Data
useSchemaOrg() accepts refs and computed getters:
const article = ref({
title: 'My Article',
description: 'Article description'
})
useSchemaOrg([
defineArticle({
headline: () => article.value.title,
description: () => article.value.description,
})
])Entity Linking
Instead of nesting everything inside one object, use @id to link separate nodes into a graph. useSchemaOrg() does this automatically when you reuse the same @id, or you can link nodes manually:
useSchemaOrg([
// Define the Person separately
definePerson({
'@id': '#jane', // Unique ID
'name': 'Jane Doe',
'jobTitle': 'Editor',
}),
// Link the Article to the Person via ID
defineArticle({
headline: 'Advanced Schema Patterns',
author: { '@id': '#jane' }, // Links to the node above
}),
])This produces one connected JSON-LD graph instead of duplicate, disconnected nodes.
Site-Wide Setup
Set up base schema in your root component or layout. Child components can add specific types that link to this graph automatically.
<script lang="ts" setup>
const route = useRoute()
useHead(() => ({
templateParams: {
schemaOrg: {
host: 'https://mysite.com',
path: route.path,
inLanguage: 'en',
}
}
}))
useSchemaOrg([
defineWebPage(),
defineWebSite({
name: 'My Site',
description: 'What my site does.',
}),
// Use defineOrganization for businesses, definePerson for personal sites
defineOrganization({
name: 'My Company',
logo: '/logo.png',
})
])
</script>If using the Nuxt Schema.org module, the module handles this configuration automatically in nuxt.config.ts:
export default defineNuxtConfig({
modules: ['nuxt-schema-org'],
site: {
url: 'https://mysite.com',
name: 'My Site',
},
schemaOrg: {
identity: {
type: 'Organization',
name: 'My Company',
logo: '/logo.png',
}
}
})Blog Article Example
Nuxt's hierarchical head system means you don't need to repeat WebSite and WebPage if they're in the layout:
<script lang="ts" setup>
const route = useRoute()
const { data: article, error } = await useFetch<{
title: string
image: string
publishedAt: string
updatedAt: string
author: { name: string, url: string }
}>(`/api/articles/${encodeURIComponent(String(route.params.slug))}`)
if (error.value)
throw error.value
if (!article.value) {
throw createError({ statusCode: 404, statusMessage: 'Article not found' })
}
useSchemaOrg([
defineArticle({
headline: article.value.title,
image: article.value.image,
datePublished: article.value.publishedAt,
dateModified: article.value.updatedAt,
author: {
name: article.value.author.name,
url: article.value.author.url,
}
})
])
</script>Breadcrumb Schema
Breadcrumb schema tells Google the structure of your site. Instead of showing a raw URL like example.com/features/color-extraction in search results, Google displays a path: Home > Features > Color Extraction.
Google stopped showing breadcrumbs on mobile search results in January 2025, replacing them with a domain-only label; desktop results and the Search Console breadcrumb report are unaffected. Breadcrumb markup describes your hierarchy; it does not guarantee AI visibility.
<script lang="ts" setup>
const route = useRoute()
useSchemaOrg([
defineBreadcrumb({
itemListElement: [
{ name: 'Home', item: 'https://mysite.com' },
{ name: 'Features', item: 'https://mysite.com/features' },
{ name: route.params.slug, item: `https://mysite.com${route.path}` },
],
}),
])
</script>For dynamic breadcrumbs generated from the route path:
export function useDynamicBreadcrumbs() {
const route = useRoute()
const segments = route.path.split('/').filter(Boolean)
const items = segments.map((segment, index) => ({
name: segment.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase()),
item: `https://mysite.com/${segments.slice(0, index + 1).join('/')}`,
}))
useSchemaOrg([
defineBreadcrumb({ itemListElement: [{ name: 'Home', item: 'https://mysite.com' }, ...items] }),
])
}FAQ Schema
FAQPage markup previously supported expandable Q&As. It no longer produces that Google rich result on any site: Google restricted the feature to government and health sites in 2023, then retired it in May 2026. Use defineQuestion() for each question/answer pair if you still want the semantic markup:
<script lang="ts" setup>
useSchemaOrg([
defineQuestion({
name: 'What browsers does PocketUI work with?',
acceptedAnswer: 'Chrome, Brave, Arc, Edge, and any Chromium-based browser.',
}),
defineQuestion({
name: 'Is there a free plan?',
acceptedAnswer: 'Yes. The free plan includes color extraction and font detection with up to 10 saves per month.',
}),
])
</script>For dynamic FAQs from a CMS or API:
<script lang="ts" setup>
const { data: faqs } = await useFetch('/api/faqs')
useSchemaOrg(
(faqs.value || []).map(faq => defineQuestion({
name: faq.question,
acceptedAnswer: faq.answer,
}))
)
</script>Don't add FAQPage markup expecting an AI citation boost either: an Ahrefs study of 1,885 pages that added JSON-LD schema between August 2025 and March 2026 found no meaningful lift in Google AI Overview or ChatGPT citations. For a broader strategy on AI visibility, see AI-optimized content.
Testing Your Markup
Validate your markup with Google's Rich Results Test. For a full testing workflow including Search Console monitoring, see Rich Results.
For local development, two options catch problems before you deploy:
- Nuxt DevTools: the Nuxt Schema.org module adds a Schema.org tab that renders the resolved graph, flags invalid entries, and shows the raw JSON-LD without a deploy round-trip
- Manual inspection: open the
<head>and confirm the JSON-LD script tag is present and valid JSON
Checklist
- Install the Nuxt Schema.org module, or
@unhead/schema-orgmanually - Set up base
WebSite,WebPage, andOrganization/Personschema site-wide - Add page-specific schema (Article, Product, Breadcrumb) with the
defineX()helpers - Link related entities with a shared
@idinstead of nesting duplicates - Skip
FAQPagemarkup if you're chasing a rich result: it no longer produces one - Validate markup with Google's Rich Results Test
Pre-launch setup, post-launch verification, and ongoing monitoring. Interactive checklist with links to every guide.