nuxt-schema-org
Requires Nuxt ^4.6.0 || ^5.0.0 and Unhead 3.4.2 or newer.
The module renders one Schema.org @graph per page as <script type="application/ld+json">.
It wraps Unhead Schema.org and adds site config, Nuxt Content, and i18n. Docs: https://nuxtseo.com/docs/schema-org
Setup
Install with npx nuxt module add schema-org. Config key: schemaOrg.
Included in @nuxtjs/seo v6. If the app uses @nuxtjs/seo, configure the schemaOrg key and do not add the module again.
Set site.url. Every @id and relative URL resolves against it. site.name and site.description feed the WebSite node.
The module needs SSR. Crawlers read only the SSR response. With ssr: false the module warns in dev.
Automatic behaviour
With defaults: true (the default), every page gets:
WebSitefromsite.name,site.description, and the current locale.WebPagefor the page canonical link, or the current URL without one. Itsnamecomes from the page<title>.- The identity node, if
schemaOrg.identityis set.WebSite.publisherandWebPage.aboutpoint to it.
WebPage picks a subtype from the last path segment: about, about-us to AboutPage; contact, contact-us, get-in-touch to ContactPage; faq to FAQPage; search to SearchResultsPage; checkout to CheckoutPage.
The resolver links nodes. Product.brand, Article.publisher, and Article.author fall back to #identity. mainEntityOfPage points to the page WebPage.
Nodes with the same @id merge. defineWebPage({ name: 'X' }) extends the default WebPage; it does not add a second one.
Enum values take the short form: availability: 'InStock' renders as https://schema.org/InStock.
Set schemaOrg: { defaults: false } to emit only the nodes you add.
Site identity
Put a static identity in nuxt.config.ts as a plain object with type ('Organization' | 'Person' | 'LocalBusiness').
For a LocalBusiness subtype, keep type: 'LocalBusiness' and add '@type':
export default defineNuxtConfig({
schemaOrg: {
identity: {
'type': 'LocalBusiness',
'@type': 'Restaurant', // any https://schema.org/LocalBusiness#subtypes
'name': 'The Coastal Kitchen',
'address': { streetAddress: '742 Oceanview Blvd', addressLocality: 'Santa Cruz', addressCountry: 'US' },
'openingHoursSpecification': [{ dayOfWeek: ['Monday', 'Tuesday'], opens: '11:30', closes: '22:00' }],
},
},
})The identity @type renders as ["Organization", "LocalBusiness", "Restaurant"].
String subtypes such as 'OnlineStore' are not valid type values.
For an online store, use type: 'Organization' with '@type': ['Organization', 'Store', 'OnlineStore'].
If identity depends on runtime data, call useSchemaOrg([defineOrganization({...})]) in app.vue.
A defineOrganization or definePerson node in the graph becomes the #identity node.
Add nodes on a page
useSchemaOrg() and every defineX() are auto-imported. Pass an array.
<script setup lang="ts">
const { data: product } = await useFetch('/api/product')
useSchemaOrg([
defineProduct({
name: product.value!.name,
description: product.value!.description,
image: product.value!.image, // relative paths resolve against site.url
offers: [{ price: product.value!.price, priceCurrency: 'USD', availability: 'InStock' }],
}),
])
</script>Await data before useSchemaOrg(). In production the graph renders once on the server. Data fetched in onMounted never reaches crawlers.
Refs, getters, and computed() work for the whole array or for single fields.
FAQ: set the WebPage type, then add questions. They attach to WebPage.mainEntity.
useSchemaOrg([
defineWebPage({ '@type': 'FAQPage' }),
defineQuestion({ name: 'Do you ship abroad?', acceptedAnswer: 'Yes, to 30 countries.' }),
])Breadcrumbs: the last itemListElement may omit item. The resolver adds position.
Custom node: pass a plain object with '@type'. Pass the items of a @graph array, never a full JSON-LD document.
Components exist for templates, such as <SchemaOrgArticle />. Prefer useSchemaOrg() in script code.
Nuxt Content and i18n
Nuxt Content v3 needs a schema change before the schemaOrg frontmatter key renders. See references/nuxt-content.md.
With @nuxtjs/i18n, each locale gets its own WebSite node linked by workTranslation.
Per-locale name and description come from nuxtSiteConfig.name and nuxtSiteConfig.description in the locale messages.
The config identity is shared across locales. For translated identity fields, call defineOrganization({ name: () => t('...') }) in app.vue.
Traps
defineLocalBusiness({ '@type': 'Restaurant' })as the config identity loses the LocalBusiness resolver. It renders["Organization", "Restaurant"]and leavesopeningHoursSpecificationunnormalized. Use the plain object form above.- Type names are case sensitive.
FAQPageis valid.FaqPagerenders as written and is not a schema.org type. - Content v3 without a
headfield in the collection schema drops theschemaOrgfrontmatter. No error, no node. See references/nuxt-content.md. - A node without
'@type'still renders, and search engines ignore it. The module warns in dev only. - A LocalBusiness identity with
logoadds a second node,#organization. The logo goes on that node, not on#identity. This is intentional upstream: unhead emits it for Google Logo rich results.
Version limits
v7 breaking changes and their fixes:
- Nuxt 3 is not supported. Upgrade to Nuxt
^4.6.0 || ^5.0.0, Vue^3.5.0, and Node^22.22.3 || ^24.15.0 || >=26.0.0. - Unhead 2 is not supported. Nuxt 4.6 already ships Unhead 3.4.2 or later. Remove any Unhead 2 pin.
- Site Config 5 is required. If the app depends on
nuxt-site-configdirectly, upgrade it to v5. - Nuxt Content v2 is not supported. Nuxt stops at startup with a version error. Upgrade to Nuxt Content v3.
- The
#schema-orgruntime directory alias is gone. Import from#schema-org/app,#schema-org/server, or#schema-org/types. - With
reactive: false, client code calls touseSchemaOrg()do nothing. The functions and components leave the client bundle. SetschemaOrg: { reactive: true }to update the graph on the client. - The DevTools panel needs
nuxtseo-devtools-host. Runpnpm add -D nuxtseo-devtools-host@^1.
The module vendors Unhead Schema.org 3 and aliases @unhead/schema-org to that copy.
Full node list: https://unhead.unjs.io/docs/schema-org/api/schema/organization
Config
defaults(true): addWebSite,WebPage, and identity.reactive(dev, orssr: false): ship schema code to the client and update on navigation. SEO does not need it.- Other options: https://nuxtseo.com/docs/schema-org/api/config
To change the meta that feeds every node, such as host or url, edit the object that the schema-org:meta hook receives in a Nuxt plugin. The hook may be async. A hook url sets host and path and wins over a canonical link.
Debug
/__schema-org__/debug.jsonin dev, or in production withdebug: true.- Nuxt DevTools has a Schema.org tab. The panel needs
nuxtseo-devtools-hostas a dev dependency. In dev the client shows a reactive graph; crawlers see only the SSR script.