How to Add Structured Data (JSON-LD) to a Next.js Site and Validate It


Structured data has always mattered to search engines. When a search engine reads your page, it sees HTML: a heading, some paragraphs, a date somewhere, a name somewhere else.

Structured data tells it directly what those pieces are. This page is an article, this is its headline, this person wrote it, and it was published on this date.

For me, search engine optimization isn’t a box to tick at the end. I want the products and sites I build to be found, and to be understood correctly when they are.

That’s why I add structured data to every page where it makes sense. And I think it’s becoming more relevant, not less, as AI systems start answering questions from web content.

In a Next.js app, nothing writes that data for you. You add it yourself, as a small block of JSON in the pages that need it.

In this tutorial, you’ll learn what structured data is and how JSON-LD works. Then you’ll add it to a Next.js App Router project: a reusable JsonLd component that’s safe to use with any data, organization and article data built from the same content the page renders, and a check that validates all of it after every build.

Table of Contents

Prerequisites

To follow along, you’ll need:

  • A Next.js app that uses the App Router and TypeScript. The code in this article was tested with Next.js 16.4.0 and React 19.3.

  • Basic familiarity with Server Components and dynamic routes.

  • The schema-dts package, which gives you TypeScript types for structured data.

Install schema-dts as a development dependency, since it only provides types:

npm install -D schema-dts

The examples use a blog. Wherever you see getPost(), use your own data source: a CMS, a database, or Markdown files.

Structured Data Basics for Developers

If you’ve never worked with structured data, this section gives you just enough to understand the code that follows.

What Structured Data and JSON-LD Are

Structured data is a machine-readable description of what a page is about. Instead of leaving a search engine to work out that “Jane Doe” is the author and “2026-09-01” is the publication date, you state it in a format a program can read without guessing.

The words you use come from Schema.org, a shared vocabulary of types (like Article, Person and Organization) and their properties (like headline, author and datePublished). It was started by Google, Microsoft, Yahoo and Yandex, and it’s the vocabulary search engines expect.

JSON-LD (JavaScript Object Notation for Linked Data) is one way to write that data. It’s a W3C standard, and on a web page it lives in a script tag with its own type:


The browser doesn’t run this script. A type that isn’t JavaScript turns the tag into what the HTML standard calls a data block, “not processed by the user agent, but instead by author script or other tools.”

There are two other formats, Microdata and RDFa, which add attributes to your existing HTML elements. Google supports all three, but it recommends JSON-LD “if your site’s setup allows it.” For a React developer, JSON-LD is also the natural fit: it’s a plain object you build from data, separate from the JSX that renders the page.

How a JSON-LD Block Is Built

Here’s a JSON-LD block for a blog post:

{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "How Caching Works in Next.js",
  "datePublished": "2026-09-01T09:00:00.000Z",
  "author": {
    "@type": "Person",
    "name": "Jane Doe"
  },
  "publisher": {
    "@type": "Organization",
    "@id": "https://example.com/#organization",
    "name": "Example Dev Blog"
  }
}

The keys that start with @ are JSON-LD keywords. The rest are Schema.org properties:

  • @context says which vocabulary the names come from. For Schema.org, it’s https://schema.org.

  • @type says what kind of thing this is. BlogPosting is a more specific kind of Schema.org Article.

  • headline, datePublished and author are properties of that type. A property can hold text, a date, a URL, or another typed object, like the Person in author.

  • @id gives a thing a unique name, usually a URL with a # fragment. Here, it marks the publisher as the same organization that another block, on the home page, describes with the same @id.

You’ll use all four in the code that follows.

Structured Data, JSON-LD, Schema.org, and Rich Results

These four terms get mixed up a lot, so here’s how they relate:

Term What it is
Structured data The idea: a machine-readable description of a page
Schema.org The vocabulary: the types and properties you can use
JSON-LD The format: how you write that description into the page
Rich results A Google Search feature: enhanced results, like a breadcrumb trail or a product’s price and rating, that structured data can make a page eligible for

In short, you write structured data in JSON-LD, using the Schema.org vocabulary, and Google may use it to show a rich result.

What Structured Data Does for Search (and What It Doesn’t)

Google says it uses structured data “to understand the content of the page,” and to gather information about the people, books, and companies the markup describes. It also uses it to decide whether a page is eligible for rich results.

Eligible is the important word. Valid JSON-LD doesn’t guarantee a rich result, for four reasons:

  1. Each rich result has its own required properties, listed in Google’s documentation for that feature. Markup can be valid Schema.org and still miss them.

  2. Google decides whether to show the feature. Its structured data guidelines say: “Google does not guarantee that your structured data will show up in search results, even if your page is marked up correctly.”

  3. The markup has to describe content that’s visible on the page. The same guidelines say: “Don’t mark up content that is not visible to readers of the page.”

  4. Some types no longer produce a rich result at all. Google stopped showing FAQ rich results on May 7, 2026, and How-to rich results in 2023.

Structured data isn’t a ranking boost, either. As Google’s John Mueller put it in 2025: “Structured data won’t make your site rank better.”

What about AI search? Google says there’s “no special schema.org structured data that you need to add” to appear in its AI features. Bing’s webmaster guidelines say structured data “may support clearer grounding but does not guarantee visibility.” OpenAI, Anthropic, and Perplexity don’t document using it: their crawler documentation covers crawler access, not markup.

There’s a lot of hype around AI search right now, and some of it is justified. But my reasoning doesn’t depend on any one company’s promise.

I want my content to be as understandable as possible to every system that reads it, whether that’s a search engine or an AI tool. Structured data is a clear, accurate description of a page, and that’s worth having whoever reads it. Just don’t treat it as a trick for getting into AI answers.

How common is it in practice? In the State of Websites 2026 report from Greadme (the website audit tool I build), 63% of 387 audited homepages had structured data. Most of it was well formed: 91% of those sites passed validation with no errors.

But 16% of them still declared FAQPage, a type whose rich result Google had limited to government and health sites in 2023 and has since retired. Valid isn’t the same as useful.

How to Build a Safe JsonLd Component

The Next.js JSON-LD guide recommends rendering structured data “as a to find where the tag ends, and it stops there, even in the middle of a JSON string.

Say a product name comes from a CMS, and someone saves this as the name:

Blue Mug 

With plain JSON.stringify, the server sends this:

"}

The browser ends the JSON-LD tag at the first . Then it finds a second, ordinary script tag and runs it. That's a cross-site scripting (XSS) attack, and your structured data is now broken JSON as well.

I ran that page through jsdom, which parses HTML the way the HTML standard specifies. The alert ran once, and JSON.parse on the JSON-LD failed with "Unterminated string in JSON."

With the escape, the same value becomes:


There's no left for the browser to find. \u003c is JSON's own escape for <, so any JSON parser reads it back as the original character. In the same test, nothing ran, and JSON.parse returned the exact name the CMS stored.

You don't need an attacker for this to break a page. The HTML standard also lists



Source link

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top