Add JSON-LD Structured Data in Vue
- Install
@unhead/schema-orgmanually in Vue (Nuxt SEO includes it automatically) - JSON-LD is Google's recommended format for structured data
- Use
useSchemaOrg()for type-safe markup with automatic graph linking
Schema.org structured data helps Google display Rich Results: star ratings, recipe cards, and product prices. In Vue, you need to install @unhead/schema-org separately and configure it in your app entry point; Nuxt SEO handles this automatically. 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": "Add JSON-LD Structured Data in Vue",
"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.
Setup with Unhead
Vue uses Unhead for head management. You can add JSON-LD via useHead(), but useSchemaOrg() from @unhead/schema-org provides type safety and automatic graph linking.
pnpm add @unhead/vue @unhead/schema-orgInstall the head instance on your Vue app before calling the composables. Configure your public host:
import { UnheadSchemaOrg } from '@unhead/schema-org/vue'
import { createHead } from '@unhead/vue/client'
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const head = createHead({
plugins: [UnheadSchemaOrg({ host: 'https://mysite.com' })]
})
createApp(App).use(router).use(head).mount('#app')App and router are your application files. For SSR, import createHead from @unhead/vue/server. Create fresh app, router, and head instances for each request. After renderToString, use renderSSRHead to insert headTags and bodyTags into your HTML template. The Vite SSR guide covers that server integration.
import { defineArticle, useSchemaOrg } from '@unhead/schema-org/vue'
useSchemaOrg([
defineArticle({
headline: 'Add JSON-LD Structured Data in Vue',
author: { name: 'Your Name' },
datePublished: new Date(2024, 0, 15),
})
])import { useHead } from '@unhead/vue'
useHead({
script: [
{
type: 'application/ld+json',
innerHTML: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Article',
'headline': 'Add JSON-LD Structured Data in Vue',
'author': { '@type': 'Person', 'name': 'Your Name' }
})
}
],
})The defineX() helpers handle boilerplate and align with Google's Structured Data Guidelines:
| Helper | Rich Result Type |
|---|---|
defineArticle() | Article, NewsArticle, BlogPosting |
defineBreadcrumb() | Breadcrumb navigation |
defineQuestion() | FAQPage markup (no active rich result; retired May 2026) |
defineProduct() | Product listings |
Reactive Data
useSchemaOrg() accepts refs and computed getters:
<script setup lang="ts">
import { defineArticle, useSchemaOrg } from '@unhead/schema-org/vue'
import { ref } from 'vue'
const article = ref({
title: 'My Article',
description: 'Article description'
})
useSchemaOrg([
defineArticle({
headline: () => article.value.title,
description: () => article.value.description,
})
])
</script>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:
<script setup lang="ts">
import { defineArticle, definePerson, useSchemaOrg } from '@unhead/schema-org/vue'
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
}),
])
</script>This produces one connected JSON-LD graph instead of duplicate, disconnected nodes.
Site-Wide Setup
Set up base schema in your root component. The installed plugin uses your public host. A reactive canonical link supplies the current page URL:
<script setup lang="ts">
import { defineOrganization, defineWebPage, defineWebSite, useSchemaOrg } from '@unhead/schema-org/vue'
import { useHead, useSeoMeta } from '@unhead/vue'
import { useRoute } from 'vue-router'
const route = useRoute()
useHead({
link: [{
rel: 'canonical',
href: () => new URL(route.path, 'https://mysite.com').href
}]
})
useSeoMeta({ title: () => String(route.meta.title || 'My Site') })
useSchemaOrg([
defineWebPage(),
defineWebSite({ name: 'My Site' }),
defineOrganization({ name: 'My Company', logo: '/logo.png' })
])
</script>Set route.meta.title in your router configuration. Article components can add defineArticle nodes. The rendered WebPage node should contain the current canonical URL. Test two routes independently to catch shared server state.
Blog Article Example
Vue's hierarchical head system means you don't need to repeat WebSite and WebPage if they're in the layout:
<script lang="ts" setup>
import { defineArticle, useSchemaOrg } from '@unhead/schema-org/vue'
// fetchArticle is your application's data loader, not an Unhead API.
const article = await fetchArticle()
useSchemaOrg([
defineArticle({
headline: article.title,
image: article.image,
datePublished: article.publishedAt,
dateModified: article.updatedAt,
author: {
name: article.author.name,
url: article.author.url,
}
})
])
</script>Testing Your Markup
Validate your markup with Google's Rich Results Test: it renders JavaScript, so it sees JSON-LD injected client-side. A plain curl request or "View Page Source" won't, since useSchemaOrg() writes the script tag after hydration in a client-only Vue app. For a full testing workflow, see Rich Results.
Checklist
- Install
@unhead/schema-organd configure it in your app entry point - Set up base
WebSite,WebPage, andOrganization/Personschema in your root component - 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
Nuxt SEO handles Schema.org automatically with zero config. See the Nuxt Schema.org guide for the Nuxt-specific approach.
Pre-launch setup, post-launch verification, and ongoing monitoring. Interactive checklist with links to every guide.