SEO

Structured data in Next.js: schema without guesswork

Plan and validate JSON-LD in Next.js with clear schema ownership, accurate page data, safe serialization, and checks for duplicate or misleading markup.

Apex Labs / / 7 min read

Structured data describes the entities and relationships on a page in a machine-readable format. In Next.js, implement it from the same approved data that renders the visible page, then validate the deployed result. Correct markup can make a page eligible for supported search features, but it does not guarantee a rich result or a ranking improvement.

Start with the page's actual subject

Choose a type because it describes what the page contains. An article has a headline and publication information. A business page describes an organization or a real business location. A breadcrumb describes a visitor's position in the site's hierarchy.

Do not start by collecting every type a generator can produce. A service page does not become a product review because review stars would look attractive. A page that mentions software in passing does not necessarily describe a complete software application.

Google's supported search features are a subset of the broader structured data vocabulary. Valid vocabulary and eligibility for a particular Google feature are separate checks. Begin with the current documentation for the feature you intend to support and record why it fits this page.

For local organizations, the local SEO guide covers the distinction between a real location, a service area, and a generic city-targeted page. That distinction should be resolved before schema is generated.

Give every entity a clear owner

Use Organization for the publisher or business identity, with its approved name, URL, logo, and applicable contact details. Google's Organization guide describes homepage markup as a way to clarify identity; it has no universally required properties. Add relevant facts rather than inventing identifiers or addresses to fill a template.

A common source of errors is overlapping responsibility. A root layout describes the organization, a page describes an article, and a CMS integration inserts another organization object with slightly different details. Each output may parse, yet the combined document becomes difficult to maintain.

Define ownership in the implementation plan. The site shell can own stable organization identity. The article template can own its article entity. The breadcrumb component can own the current path. A page-specific entity should not be copied into every route through an overly broad layout.

Use stable identifiers consistently when connecting entities. Decide which record supplies a business name, URL, logo, and profile links. An editor should not need to correct the same fact in multiple unrelated settings.

For multi-location sites, do not reuse one location's address across every branch. Give each real location its own approved record and a visible page that corresponds to it. If an address should not be published, do not expose it through hidden markup to fill an optional field.

Derive markup from approved content

Use one data source for visible headings, schema names, dates, and canonical URLs. This avoids a stale headline in JSON-LD after an editor changes the article title. The same principle applies to availability, opening hours, and descriptions.

Treat publication date and modification date as editorial facts. A deployment date is not automatically a meaningful content update. Keep the original publication date when moving an existing article, and record an update only when the article itself changes.

Require review for fields that imply evidence. Ratings, review counts, awards, credentials, and prices should come from real records with a reason to appear on the page. Leaving an unsupported optional property out is better than filling it with plausible copy.

Google's structured data policies require representative, relevant markup and warn that correct syntax alone does not guarantee appearance. Build that principle into the publishing checklist instead of relying on a technical validator to catch editorial misrepresentation.

Match Product markup to a real product

Google distinguishes product snippets from merchant listings, which support pages where customers can purchase products. Product rich results concern a specific product or variants of that product, rather than a broad category or a general marketing page.

For product snippets, name and at least one of offers, review, or aggregateRating are required. An offer needs an actual price; provide its currency and keep availability aligned with the visible offer. “Contact sales” is not a zero-price offer. Review the relevant feature's current requirements before release.

Do not manufacture a Product entity, stock status, or review collection for an agency service page merely to obtain stars. Even when a service fits broader Schema.org vocabulary, that does not establish eligibility for Google's product treatment. Use approved product records and genuine review evidence, or leave unsupported properties out.

Separate FAQ vocabulary from Google features

As checked in October 2026, Google's documentation changelog says FAQ rich results stopped appearing on May 7, 2026. The earlier health/government restriction is therefore not a current eligibility route. The former FAQ guide now redirects to Google's documentation updates.

FAQPage still exists in Schema.org. A valid vocabulary type does not mean Google offers a corresponding rich result. Keep useful answers on a page for readers, and maintain FAQ markup only for a defined consumer or documentation purpose. Do not create repetitive question sections merely to pursue a discontinued feature.

Render JSON-LD safely

The current Next.js guide recommends rendering a JSON-LD script in a page or layout. Keep the generation close to the template that owns the relevant entity. A global script is suitable only for facts that are actually global.

Serialization deserves separate attention. The Next.js documentation warns that JSON.stringify alone does not sanitize malicious strings for insertion into a script tag. Follow a reviewed serialization approach, including handling the less-than character as documented, and treat CMS content as input rather than trusted executable markup.

Do not let editors paste arbitrary script blocks into a general content field. Prefer structured fields with validation, then generate the final JSON-LD in code. This also makes reviews easier because the application controls allowed types and properties.

If several templates share a schema builder, keep its input explicit. A function that accepts the approved article record is easier to audit than a helper that guesses publication state, hostname, and author from unrelated global settings.

This illustrative app/example/page.tsx uses a fictional, approved placeholder record for both visible content and an Article object. Replace it with validated published fields in a real application; it is not a complete article template or a guarantee of search eligibility.

const article = {
  title: 'Example guide to planning a website',
  summary: 'An illustrative guide for a website planning exercise.',
  publishedAt: '2026-10-22',
}

export default function Page() {
  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Article',
    headline: article.title,
    description: article.summary,
    datePublished: article.publishedAt,
  }

  return (
    <article>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{
          __html: JSON.stringify(jsonLd).replace(/</g, '\\u003c'),
        }}
      />
      <h1>{article.title}</h1>
      <time dateTime={article.publishedAt}>{article.publishedAt}</time>
      <p>{article.summary}</p>
    </article>
  )
}

The native script sits inside the page's server-rendered output. Only the serialized object enters the script sink; no editor-supplied script fragment is interpolated. The escaping follows the Next.js guide, while field validation and editorial approval remain separate responsibilities.

Validate syntax, meaning, and delivery

Use three layers of review. First, parse the generated JSON to catch malformed output. Second, validate the vocabulary and any supported Google feature requirements. Third, inspect the deployed page to confirm the intended markup is present and matches visible information.

Google's Rich Results Test checks supported Google features. The Schema Markup Validator helps inspect vocabulary more broadly. A clean result in one tool does not mean every property is useful or that a search feature will appear.

Test representative records rather than one perfect demo article. Include a missing optional image, a title containing punctuation, an unpublished record, and a location with unusual opening hours. These cases expose assumptions that a happy-path fixture misses.

After launch, inspect the live URL and monitor relevant Search Console reports where available. Separate a detected markup problem from a search appearance decision. If the markup is valid but no rich result appears, investigate eligibility and page quality before repeatedly adding more properties.

Maintain schema as part of the product

Schema changes when the business changes. A new address, withdrawn product, revised article, or changed site hierarchy can invalidate previously correct markup. Give those events an owner and include them in the content workflow.

Keep a small inventory of templates, emitted entities, data sources, and validation URLs. This helps a redesign preserve meaningful relationships instead of copying an old script wholesale. The migration guide explains why structured data belongs in the URL and content inventory.

Use the Next.js SEO checklist for the broader release review. Schema is strongest when it accurately describes a useful, accessible page whose URL and visible information are already clear.

References

  • nextjs
  • structured-data
  • json-ld
  • schema