X Enterprises

nuxt-x-schema

Schema.org structured data layer for Nuxt — 16 renderless JSON-LD components injected via useHead for SSR-safe rich snippet markup.

nuxt-x-schema

16 renderless Schema.org structured data components for Nuxt 4. Each component injects a <script type="application/ld+json"> tag into <head> via useHead — fully SSR-safe, visible to search engine crawlers on first paint.

Installation

npm install @xenterprises/nuxt-x-schema
// nuxt.config.ts
export default defineNuxtConfig({
  extends: ["@xenterprises/nuxt-x-schema"],
});

What the consumer writes

The minimal consumer setup is an extends entry plus an app/app.config.ts with your site's branding — no components, pages, or server code to write:

// nuxt.config.ts
export default defineNuxtConfig({
  extends: ["@xenterprises/nuxt-x-schema"],
});
// app/app.config.ts — must live in app/, not the project root
export default defineAppConfig({
  xSchema: {
    siteUrl: "https://mysite.com", // base for absolute URLs in JSON-LD
    siteName: "My Site", // default organization/publisher name
    siteLogo: "/logo.png", // optional — publisher ImageObject logo
    organizationName: "My Company", // optional — falls back to siteName
  },
});

Then drop any XSchema* component into a page. Everything is auto-imported and renderless — the only output is JSON-LD in <head>:

<XSchemaWebSite name="My Site" url="https://mysite.com" />

No environment variables are required — all configuration is via app.config.ts. If you skip the xSchema overrides, JSON-LD falls back to placeholder values (https://example.com, My Website), so set them before going live.

What This Layer Provides

  • 16 Schema.org components — all renderless, all auto-imported
  • useSchema() composable — shared utilities for URL resolution and JSON-LD injection

Configuration (app.config.ts)

export default defineAppConfig({
  xSchema: {
    siteUrl: "https://mysite.com",
    siteName: "My Site",
    siteLogo: "/logo.png",
    organizationName: "My Company",
  },
});
OptionTypeDefaultDescription
siteUrlstringundefined (runtime fallback https://example.com)Base URL for absolute URL construction.
siteNamestringundefined (runtime fallback My Website)Default organization/publisher name.
siteLogostringundefinedLogo URL; omitted from JSON-LD output when unset.
organizationNamestringundefined (falls back to siteName)Organization name.

Components

XSchemaArticle — Blog posts, news articles

PropTypeRequiredDescription
headlinestringYesArticle title.
datePublishedstringYesISO 8601 publication date.
authorstring | Person | ArrayYesAuthor name(s) or Person object(s).
descriptionstringNoArticle summary.
imagestring | string[]NoArticle image URL(s).
dateModifiedstringNoLast modification date (defaults to datePublished).
articleTypestringNoArticle, BlogPosting, NewsArticle, TechArticle (default: "BlogPosting").
urlstringNoCanonical URL.
wordCountnumberNoWord count.

XSchemaBreadcrumb — Navigation breadcrumbs

PropTypeRequiredDescription
itemsBreadcrumbItem[]YesArray of { label, to } or { name, item }. Relative URLs resolved to absolute.

XSchemaEvent — Events and conferences

PropTypeRequiredDefaultDescription
namestringYesEvent name.
descriptionstringYesEvent description.
startDatestringYesISO 8601 start date/time.
endDatestringYesISO 8601 end date/time.
eventAttendanceModestringNo"OnlineEventAttendanceMode"OnlineEventAttendanceMode, OfflineEventAttendanceMode, MixedEventAttendanceMode.
eventStatusstringNo"EventScheduled"EventScheduled, EventCancelled, EventPostponed, etc.
locationRecord<string, unknown>NoLocation object (Place or VirtualLocation).
urlstringNoEvent URL.
imagestring | string[]NoEvent image URL(s).
organizerOrganizationNoOrganizer Organization object (defaults to getDefaultOrganization()).
offersOfferNoTicket/registration Offer object.

XSchemaFAQ — FAQ pages

PropTypeRequiredDescription
itemsFAQ[]YesArray of { question, answer } (also accepts label/content or name/text).
pageTitlestringNoPage name added to the schema.
pageDescriptionstringNoPage description.
datePublishedstringNoISO 8601 publish date.
dateModifiedstringNoISO 8601 modified date.
authorstring | PersonNoPage author.

XSchemaHowTo — Step-by-step guides

PropTypeRequiredDescription
namestringYesGuide title.
descriptionstringNoHow-to description.
imagestring | string[]NoIllustration image URL(s).
totalTimestringNoISO 8601 duration (e.g. "PT30M").
estimatedCostMonetaryAmountNoEstimated cost object { currency, value }.
supplystring[]NoRequired supplies (strings are wrapped as HowToSupply).
toolstring[]NoRequired tools (strings are wrapped as HowToTool).
stepsHowToStep[]YesArray of { name, text, image?, url? }.

XSchemaLocalBusiness — Business locations

PropTypeRequiredDescription
namestringYesBusiness name.
businessTypestringYesSchema.org type (e.g. "Restaurant", "CafeOrCoffeeShop", "MedicalClinic").
urlstringYesBusiness website URL.
addressPostalAddressYesAddress object: { streetAddress, addressLocality, addressRegion, postalCode, addressCountry }.
telephonestringNoPhone number.
emailstringNoContact email.
imagestringNoBusiness image URL.
descriptionstringNoBusiness description.
openingHoursSpecificationRecord<string, unknown>[]NoArray of opening hours objects.
aggregateRatingAggregateRatingNoRating object { ratingValue, reviewCount }.
priceRangestringNoPrice range indicator (e.g. "$$").
sameAsstring[]NoSocial profile and directory URLs.

XSchemaOffer — Pricing offers

PropTypeRequiredDefaultDescription
namestringYesOffer name.
descriptionstringNoOffer description.
pricestring | numberYesPrice value.
priceCurrencystringNo"USD"ISO 4217 currency code.
availabilitystringNo"InStock"InStock, OutOfStock, PreOrder, etc.
urlstringNoOffer URL.
sellerOrganizationNoSeller Organization object (defaults to getDefaultOrganization()).
validFromstringNoISO 8601 offer start date.
validThroughstringNoISO 8601 offer end date.
priceValidUntilstringNoISO 8601 price expiry date.
itemConditionstringNo"NewCondition"NewCondition, UsedCondition, RefurbishedCondition.

XSchemaOrganization — Company information

PropTypeRequiredDefaultDescription
namestringYesOrganization name.
urlstringYesWebsite URL.
logostringYesLogo image URL.
legalNamestringNoOfficial legal name.
descriptionstringNoOrganization description.
emailstringNoContact email.
telephonestringNoContact phone.
addressPostalAddressNoPostal address object.
sameAsstring[]No[]Social and directory profile URLs.
foundingDatestringNoISO 8601 founding date.
founderstring | PersonNoFounder name or Person object.

XSchemaPerson — Author/team profiles

PropTypeRequiredDefaultDescription
namestringYesPerson's full name.
jobTitlestringNoJob title.
descriptionstringNoShort bio.
imagestringNoProfile image URL.
urlstringNoPersonal website URL.
emailstringNoEmail address.
telephonestringNoPhone number.
sameAsstring[]No[]Social and profile URLs.
worksForOrganizationNoEmployer Organization object.
knowsAboutstring[]No[]Topics the person knows about.
alumniOfstring | OrganizationNoEducational institution (string wrapped as Organization).

XSchemaProduct — Products with offers

PropTypeRequiredDescription
namestringYesProduct name.
descriptionstringYesProduct description.
imagestring | string[]NoProduct image URL(s).
brandstring | OrganizationNoBrand name or Organization (defaults to siteName).
offersOfferYesOffer object with price and availability.
aggregateRatingAggregateRatingNoRating object { ratingValue, reviewCount }.
reviewReview[]NoArray of Review objects.
skustringNoStock-keeping unit identifier.
manufacturerstring | OrganizationNoManufacturer name or Organization.
categorystringNoProduct category.

XSchemaQAPage — Q&A pages

PropTypeRequiredDescription
itemsQAItem[]YesArray of Q&A items: { question, answer, answerAuthor?, answerDate?, upvoteCount?, answerCount?, suggestedAnswers? }. Each item accepts label/question/name for the question and content/answer/text for the accepted answer.
pageTitlestringNoPage name added to the schema.
pageDescriptionstringNoPage description.
datePublishedstringNoISO 8601 publish date.
dateModifiedstringNoISO 8601 modified date.
authorstring | PersonNoPage author.

XSchemaReview — Reviews and ratings

PropTypeRequiredDefaultDescription
itemReviewedRecord<string, unknown>YesThe item being reviewed (must include @type and name).
reviewRatingRatingYesRating object { ratingValue, bestRating? }.
authorstring | PersonYesReviewer name or Person object.
reviewBodystringNoFull review text.
datePublishedstringNoISO 8601 review date.
isAggregatebooleanNofalseWhen true, outputs AggregateRating schema instead of individual Review.
ratingCountnumberNoTotal rating count (aggregate mode).
reviewCountnumberNoTotal review count (aggregate mode).

XSchemaService — Service pages

PropTypeRequiredDescription
namestringYesService name.
descriptionstringYesService description.
serviceTypestringYesService type identifier.
providerOrganizationNoProvider Organization object (defaults to getDefaultOrganization()).
areaServedstring | string[]NoGeographic area(s) served.
offersOfferNoPricing Offer object.
imagestring | string[]NoService image URL(s).
urlstringNoService page URL.
categorystringNoService category.

XSchemaSoftwareApplication — Web/mobile apps

PropTypeRequiredDescription
namestringYesApp name.
descriptionstringYesApp description.
urlstringYesApp URL.
applicationCategorystringYesSchema.org application category (e.g. "BusinessApplication").
operatingSystemstring | string[]NoSupported OS(es).
offersOfferNoPricing Offer object.
aggregateRatingAggregateRatingNoRating object.
authorstring | PersonNoDeveloper name or Person object.
screenshotstring | string[]NoScreenshot URL(s).
releaseNotesstringNoRelease notes URL or text.

XSchemaVideoObject — Video content

PropTypeRequiredDescription
namestringYesVideo title.
descriptionstringYesVideo description.
thumbnailUrlstring | string[]YesThumbnail URL(s).
uploadDatestringYesISO 8601 upload date.
durationstringNoISO 8601 duration (e.g. "PT10M30S").
contentUrlstringNoDirect video URL.
embedUrlstringNoEmbed URL (e.g. YouTube embed).
interactionCountnumberNoNumber of views/interactions.
authorstring | PersonNoCreator name or Person object.

XSchemaWebSite — Site-wide schema

PropTypeRequiredDefaultDescription
namestringYesSite name.
urlstringYesSite URL.
descriptionstringNoSite description.
publisherOrganizationNoPublisher Organization object.
enableSearchBoxbooleanNofalseAdd Sitelinks Search Box potentialAction.
searchUrlstringNoSearch URL template (required if enableSearchBox is true; must include {search_term_string} placeholder).
queryInputstringNo"required name=search_term_string"query-input value for the SearchAction.

useSchema() Composable

MethodReturnsDescription
siteUrlComputedRef<string>Configured site URL (no trailing slash).
siteNameComputedRef<string>Configured site name.
configComputedRef<XSchemaConfig>Reactive app config under the xSchema namespace.
resolveUrl(path)string | undefinedConvert relative URL to absolute.
getDefaultOrganization()OrganizationDefault org from app config.
getDefaultPublisher()objectDefault publisher with ImageObject logo.
normalizeAuthor(author)Person | Person[] | undefinedConvert string/array to Person schema.
normalizeImage(image)string[] | undefinedEnsure image is array.
useSchemaHead(fn)voidInject JSON-LD into <head> via useHead.

Environment Variables

None required. All configuration is via app.config.ts.

How It Works

Each XSchema* component calls useSchema().useSchemaHead() which wraps useHead() to inject a <script type="application/ld+json"> tag into the page <head>. Because useHead is SSR-safe, the JSON-LD is rendered server-side and visible to crawlers on first paint. Default values for publisher/organizer come from app.config.ts via useSchema(), so branding is configured once and reused across all components. String values for author, founder, and brand are automatically converted to proper Schema.org typed objects.

Test your output: Google Rich Results Test · Schema.org Validator

Layer Architecture

PathPurpose
nuxt.config.tsMinimal layer entry — just devtools: { enabled: false }.
app.config.tsDefault xSchema namespace (siteUrl, siteName, siteLogo, organizationName) with TypeScript type augmentation.
app/components/X/Schema/16 auto-imported XSchema-prefixed components, one per Schema.org @type.
app/composables/useSchema.tsShared helpers: resolveUrl, normalizeAuthor, normalizeImage, getDefaultOrganization, getDefaultPublisher, useSchemaHead.
app/types/schema.tsTypeScript interfaces: Person, Organization, ImageObject, PostalAddress, BreadcrumbItem, FAQ, QAItem, HowToStep, MonetaryAmount, Offer, AggregateRating, Rating, Review, OpeningHoursSpecification, SchemaConfig.
.playground/Dev playground with one demo page per component.
tests/Vitest suite (components.test.ts, useSchema.test.ts).

Rendering contract

Every component follows the same pattern — no visible <template> block (most are renderless); useSchemaHead(() => schemaObject) inside <script setup lang="ts"> is what produces the JSON-LD. Components are never wrapped in <ClientOnly> — the useHead() injection is SSR-native and must be in the first-byte HTML.


AI Context

package: "@xenterprises/nuxt-x-schema"
version: "0.3.0"
use-when: >
  Adding Schema.org structured data to a Nuxt 4 site for SEO and
  answer-engine optimization (AEO). 16 renderless XSchema* components
  (auto-imported with the XSchema prefix) cover the common @types —
  Article, Breadcrumb, Event, FAQ, HowTo, LocalBusiness, Offer,
  Organization, Person, Product, QAPage, Review, Service,
  SoftwareApplication, VideoObject, WebSite. Each component injects a
  <script type="application/ld+json"> tag into <head> via useHead, so
  JSON-LD is present in SSR/SSG HTML from the first byte — never behind
  <ClientOnly>. Configure branding once under the xSchema namespace in
  app.config.ts (siteUrl, siteName, siteLogo, organizationName); the
  useSchema() composable exposes resolveUrl(), getDefaultPublisher(),
  getDefaultOrganization(), normalizeAuthor(), normalizeImage() for
  custom schemas and as the building blocks for the bundled components.
  Schema.org type interfaces live in app/types/schema.ts.
Copyright © 2026