---
title: "JSON-LD Structured Data in Nuxt"
description: "Learn how to implement Schema.org structured data in Nuxt. Get rich results in Google search with type-safe JSON-LD markup using the Nuxt Schema.org module."
canonical_url: "https://nuxtseo.com/learn-seo/nuxt/mastering-meta/schema-org"
last_updated: "2025-12-17"
---

<key-takeaways>

- `useSchemaOrg()` provides type-safe JSON-LD with automatic graph linking
- Nuxt Schema.org module sets up WebSite and WebPage schemas automatically
- Rich results aren't guaranteed. test with Google's Rich Results Test

</key-takeaways>

Schema.org structured data helps Google display [Rich Results](https://developers.google.com/search/docs/appearance/structured-data/search-gallery). stars, FAQs, recipes, product prices. Rotten Tomatoes saw a [25% higher click-through rate](https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data) on pages with structured data compared to pages without.

```html
<!-- 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](https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data) as the easiest format to implement and maintain. Google supports rich results for many schema types including Article, Product, FAQ, and Breadcrumb. See [Rich Results](/learn-seo/nuxt/mastering-meta/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](https://developers.google.com/search/docs/appearance/structured-data/sd-policies).

## Manual Implementation

Nuxt includes [Unhead](https://unhead.unjs.io/) for head management. Add JSON-LD directly via `useHead()`:

```vue [pages/blog/[slug].vue]
<script lang="ts" setup>
const { data: article } = await useFetch(`/api/articles/${route.params.slug}`)

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](https://schema.org/docs/data-and-datasets.html), use `useSchemaOrg()` from `@unhead/schema-org`:

```bash
pnpm add -D @unhead/schema-org
```

See [Unhead Schema.org setup](https://unhead.unjs.io/schema-org/getting-started/setup) for configuration.

## Automated Setup with Nuxt Schema.org

The Nuxt Schema.org module handles structured data automatically with zero config:

<module-card className="w-1/2" slug="schema-org">



</module-card>

Or install the full Nuxt SEO module which includes Schema.org plus sitemaps, robots.txt, and OG images:

<module-card className="w-1/2" slug="nuxt-seo">



</module-card>

The module sets up base schema (WebSite, WebPage, Organization/Person) automatically and provides type-safe helpers for all common schema types.

<code-group>

```ts [useSchemaOrg (recommended)]
import { defineArticle, useSchemaOrg } from '@unhead/schema-org/vue'

useSchemaOrg([
  defineArticle({
    headline: 'JSON-LD Structured Data in Nuxt',
    author: { name: 'Your Name' },
    datePublished: new Date(2024, 0, 15),
  })
])
```

```ts [useHead (manual)]
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' }
      })
    }
  ],
})
```

</code-group>

The `defineX()` helpers align with [Google's Structured Data Guidelines](https://developers.google.com/search/docs/guides/sd-policies) and handle boilerplate:

<table>
<thead>
  <tr>
    <th>
      Helper
    </th>
    
    <th>
      Rich Result Type
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <a href="https://unhead.unjs.io/schema-org/schema/article" rel="nofollow">
        <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
          <span class="s0YkB">
            defineArticle
          </span>
          
          <span class="sqjlB">
            ()
          </span>
        </code>
      </a>
    </td>
    
    <td>
      Article, NewsArticle, BlogPosting
    </td>
  </tr>
  
  <tr>
    <td>
      <a href="https://unhead.unjs.io/schema-org/schema/breadcrumb" rel="nofollow">
        <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
          <span class="s0YkB">
            defineBreadcrumb
          </span>
          
          <span class="sqjlB">
            ()
          </span>
        </code>
      </a>
    </td>
    
    <td>
      Breadcrumb navigation
    </td>
  </tr>
  
  <tr>
    <td>
      <a href="https://unhead.unjs.io/schema-org/schema/question" rel="nofollow">
        <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
          <span class="s0YkB">
            defineQuestion
          </span>
          
          <span class="sqjlB">
            ()
          </span>
        </code>
      </a>
    </td>
    
    <td>
      FAQ pages
    </td>
  </tr>
  
  <tr>
    <td>
      <a href="https://unhead.unjs.io/schema-org/schema/product" rel="nofollow">
        <code className="language-ts shiki shiki-themes github-light github-light material-theme-palenight" language="ts" style="">
          <span class="s0YkB">
            defineProduct
          </span>
          
          <span class="sqjlB">
            ()
          </span>
        </code>
      </a>
    </td>
    
    <td>
      Product listings
    </td>
  </tr>
</tbody>
</table>

## Reactive Data

`useSchemaOrg()` accepts refs and computed getters:

```ts
const article = ref({
  title: 'My Article',
  description: 'Article description'
})

useSchemaOrg([
  defineArticle({
    headline: () => article.value.title,
    description: () => article.value.description,
  })
])
```

## 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.

```vue [app.vue]
<script lang="ts" setup>
import { defineOrganization, defineWebPage, defineWebSite, useSchemaOrg } from '@unhead/schema-org/vue'

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`:

```ts [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:

```vue [pages/blog/[slug].vue]
<script lang="ts" setup>
import { defineArticle, useSchemaOrg } from '@unhead/schema-org/vue'

const { data: article } = await useFetch(`/api/articles/${route.params.slug}`)

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 nice path: Home > Features > Color Extraction.

One ecommerce case study found that adding full-hierarchy breadcrumbs to product listing pages [increased CTR by 10% and organic traffic by 16%](https://www.searchenginejournal.com/breadcrumbs-seo/255007/).

Note: Google [removed visible breadcrumbs from mobile SERPs](https://www.searchenginejournal.com/google-search-breadcrumb-changes-mobile/) in January 2025, replacing them with domain-only labels. The schema still powers desktop breadcrumb rich results and helps AI engines understand your site hierarchy.

```vue [pages/features/[slug].vue]
<script lang="ts" setup>
import { defineBreadcrumb, useSchemaOrg } from '@unhead/schema-org/vue'

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:

```ts [composables/useBreadcrumbs.ts]
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

FAQ schema makes your search listing expandable with dropdown Q&As. Use `defineQuestion()` for each question/answer pair:

```vue [pages/pricing.vue]
<script lang="ts" setup>
import { defineQuestion, useSchemaOrg } from '@unhead/schema-org/vue'

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:

```vue [pages/faq.vue]
<script lang="ts" setup>
const { data: faqs } = await useFetch('/api/faqs')

useSchemaOrg(
  (faqs.value || []).map(faq => defineQuestion({
    name: faq.question,
    acceptedAnswer: faq.answer,
  }))
)
</script>
```

Google [removed FAQ rich results](https://developers.google.com/search/blog/2023/08/howto-faq-changes) for most sites in 2023, but the markup [still drives AI citations](/learn-seo/nuxt/mastering-meta/rich-results). Pages with FAQPage markup are [3.2x more likely to be cited in Google AI Overviews](https://www.onely.com/blog/how-to-rank-in-google-ai-overviews/) than pages with unstructured text alone, and over [92% of AI Overview citations](https://www.seoclarity.net/blog/google-ai-overview-study) come from domains already ranking in the top 10. For a broader strategy on maximizing AI visibility, see [AI-optimized content](/learn-seo/nuxt/launch-and-listen/ai-optimized-content).

## Testing Your Markup

Validate your markup with [Google's Rich Results Test](https://search.google.com/test/rich-results). For a full testing workflow including Search Console monitoring, see [Rich Results](/learn-seo/nuxt/mastering-meta/rich-results#testing-your-markup).

For local development, inspect the `<head>` to verify the JSON-LD script tag is present and valid JSON.
