Next.js  

Type-Safe JSON-LD for Next.js: A Developer's Guide to Structured Data

Introduction

As developers, we spend countless hours optimising our applications for performance, accessibility, and user experience. Yet there’s one optimization that often gets overlooked: structured data. It’s the secret sauce that helps search engines and AI systems understand your content, but implementing it correctly has traditionally been painful.

In this article, we’ll explore how to add type-safe JSON-LD structured data to your Next.js applications using Schema Sentry—a library designed to make structured data generation as easy as writing TypeScript.

What is Structured Data and Why Should You Care?

Structured data is a standardised format (JSON-LD) that describes your page content to machines. Think of it as metadata that tells Google, Bing, ChatGPT, and other systems:

  • What your page is about

  • Who wrote it

  • When it was published

  • What product you’re selling

  • How to navigate your site

Real-World Impact

Without structured data, search engines guess. With it, they know. This leads to:

  • Rich search results - Articles with publication dates, products with prices, FAQs with expandable answers

  • Better AI visibility - ChatGPT, Claude, and Perplexity use structured data to cite and recommend content

  • Higher click-through rates - Rich snippets stand out in search results

The Traditional Approach (And Its Problems)

Here’s how most developers add structured data today:

<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{
    __html: JSON.stringify({
      "@context": "https://schema.org",
      "@type": "Article",
      "headline": "My Blog Post",
      "author": {
        "@type": "Person",
        "name": "Jane Doe"
      },
      "datePublished": "2026-02-10"
    })
  }}
/>

What’s Wrong With This?

  • No type safety - Typo in "@type": "Aticle"? Good luck finding it

  • No validation - Invalid schema? You’ll only know when Google rejects it

  • Maintenance nightmare - Change a field name? Update it everywhere manually

  • No autocomplete - IDE can’t help you remember schema.org field names

These aren’t just annoyances—they directly impact your SEO and AI discoverability.

Introducing Schema Sentry

Schema Sentry solves these problems by providing type-safe builders for JSON-LD structured data. It works with both Next.js App Router and Pages Router.

Installation

# Using npm
npm install @schemasentry/next
npm install -D @schemasentry/cli

# Using pnpm
pnpm add @schemasentry/next
pnpm add -D @schemasentry/cli

# Using yarn
yarn add @schemasentry/next
yarn add -D @schemasentry/cli

Basic Usage

Let’s start with a simple example—adding Article schema to a blog post:

App Router (Next.js 13+)

// app/blog/[slug]/page.tsx

import { Schema, Article, BreadcrumbList } from "@schemasentry/next";

export default function BlogPost({ params }: { params: { slug: string } }) {

  const article = Article({
    headline: "Getting Started with Next.js",
    authorName: "Jane Doe",
    datePublished: "2026-02-10",
    dateModified: "2026-02-10",
    url: `https://example.com/blog/${params.slug}`,
    description: "Learn Next.js from scratch",
    image: "https://example.com/images/nextjs.jpg"
  });

  const breadcrumbs = BreadcrumbList({
    items: [
      { name: "Home", url: "https://example.com" },
      { name: "Blog", url: "https://example.com/blog" },
      { name: "Getting Started", url: `https://example.com/blog/${params.slug}` }
    ]
  });

  return (
    <>
      <Schema data={[article, breadcrumbs]} />
      <article>
        {/* Your content here */}
      </article>
    </>
  );
}

Pages Router (Classic Next.js)

// pages/blog/[slug].tsx

import Head from "next/head";
import { Schema, Article, BreadcrumbList } from "@schemasentry/next";

export default function BlogPost() {

  const article = Article({
    headline: "Getting Started with Next.js",
    authorName: "Jane Doe",
    datePublished: "2026-02-10",
    url: "https://example.com/blog/post",
    description: "Learn Next.js from scratch"
  });

  return (
    <>
      <Head>
        <title>Getting Started - My Blog</title>
      </Head>
      <Schema data={article} />
      <article>{/* content */}</article>
    </>
  );
}

Key difference: App Router doesn’t need next/head because it supports native metadata API.

Supported Schema Types

Schema Sentry includes builders for the most commonly used schema.org types:

1. Organization

Perfect for company information that appears on multiple pages:

import { Organization } from "@schemasentry/next";

const org = Organization({
  name: "Acme Corporation",
  url: "https://acme.com",
  logo: "https://acme.com/logo.png",
  description: "Building the future of web development",
  sameAs: [
    "https://twitter.com/acme",
    "https://linkedin.com/company/acme",
    "https://github.com/acme"
  ]
});

2. Product (E-commerce)

Essential for any e-commerce site:

import { Product } from "@schemasentry/next";

const product = {
  ...Product({
    name: "Premium Widget",
    description: "The ultimate productivity tool",
    url: "https://acme.com/products/widget",
    image: "https://acme.com/images/widget.jpg",
    brandName: "Acme",
    sku: "WIDGET-001"
  }),
  offers: {
    "@type": "Offer",
    price: "99.00",
    priceCurrency: "USD",
    availability: "https://schema.org/InStock"
  },
  aggregateRating: {
    "@type": "AggregateRating",
    ratingValue: "4.8",
    reviewCount: "1247"
  }
};

3. FAQPage

Great for help sections and documentation:

import { FAQPage } from "@schemasentry/next";

const faq = FAQPage({
  questions: [
    {
      question: "What is Schema Sentry?",
      answer: "A type-safe library for generating JSON-LD structured data."
    },
    {
      question: "Does it work with Pages Router?",
      answer: "Yes! It works with both App Router and Pages Router."
    }
  ]
});

4. HowTo

Perfect for tutorials and step-by-step guides:

import { HowTo } from "@schemasentry/next";

const howto = HowTo({
  name: "Install Schema Sentry",
  steps: [
    {
      name: "Install packages",
      text: "Run npm install @schemasentry/next"
    },
    {
      name: "Import components",
      text: "Import Schema and builders from the package"
    },
    {
      name: "Use in your pages",
      text: "Add the Schema component to your page components"
    }
  ]
});

5. BreadcrumbList

Helps search engines understand your site structure:

import { BreadcrumbList } from "@schemasentry/next";

const breadcrumbs = BreadcrumbList({
  items: [
    { name: "Home", url: "https://example.com" },
    { name: "Products", url: "https://example.com/products" },
    { name: "Widgets", url: "https://example.com/products/widgets" }
  ]
});

Combining Multiple Schemas

You can combine multiple schema types on a single page:

import { Schema, Organization, Product, BreadcrumbList } from "@schemasentry/next";

export default function ProductPage() {

  const org = Organization({
    name: "Acme Corp",
    url: "https://acme.com"
  });

  const product = Product({
    name: "Premium Widget",
    description: "...",
    url: "...",
    brandName: "Acme"
  });

  const breadcrumbs = BreadcrumbList({
    items: [
      { name: "Home", url: "..." },
      { name: "Products", url: "..." },
      { name: "Premium Widget", url: "..." }
    ]
  });

  return (
    <>
      {/* Pass an array of schemas */}
      <Schema data={[org, product, breadcrumbs]} />
      {/* Page content */}
    </>
  );
}

CI/CD Validation

Here’s where Schema Sentry really shines. You can validate your structured data as part of your CI/CD pipeline.

Step 1: Create a Manifest

Create schema-sentry.manifest.json at your project root:

{
  "routes": {
    "/": ["Organization", "WebSite"],
    "/blog": ["WebSite"],
    "/blog/getting-started": ["Article"],
    "/blog/typescript-tips": ["Article"],
    "/products/widget": ["Organization", "Product"],
    "/faq": ["FAQPage", "Organization"],
    "/howto/installation": ["HowTo", "Organization"]
  }
}

This manifest defines which schema types you expect on each route.

Step 2: Add CLI Commands

Add these scripts to your package.json:

{
  "scripts": {
    "schema:validate": "schemasentry validate --manifest ./schema-sentry.manifest.json --data ./schema-sentry.data.json",
    "schema:collect": "schemasentry collect --root ./.next/server/app --output ./schema-sentry.data.collected.json",
    "schema:drift": "schemasentry collect --root ./.next/server/app --check --data ./schema-sentry.data.json"
  }
}

Step 3: Add to GitHub Actions

Create .github/workflows/schema-check.yml:

name: Schema Validation

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  validate:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup pnpm
        uses: pnpm/action-setup@v2
        with:
          version: 8

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Build application
        run: pnpm build

      - name: Validate schema
        run: pnpm schema:validate

Now your CI will fail if:

  • A page is missing expected schema

  • Schema types don’t match the manifest

  • Invalid JSON-LD is generated

This prevents broken structured data from ever reaching production.

Why This Matters for AI

Structured data isn’t just for traditional SEO anymore. AI systems like ChatGPT, Claude, and Perplexity actively use structured data to:

  • Understand content - Know what your page is about without guessing

  • Generate accurate citations - Reference your content correctly in responses

  • Recommend your pages - Include your content in AI-generated answers

Without proper schema, your content is essentially invisible to the AI discovery layer. This is becoming increasingly important as more users turn to AI assistants for information.

Performance Considerations

Schema Sentry has zero runtime overhead. All schema generation happens at build time, and the component simply injects a tag with the JSON-LD. The resulting HTML is identical to hand-written JSON-LD—just without the errors.

Complete Working Example

Let’s put it all together with a complete product page:

// pages/products/[id].tsx or app/products/[id]/page.tsx

import Head from "next/head";
import { Schema, Organization, Product, BreadcrumbList } from "@schemasentry/next";

export default function ProductPage({ product }: { product: any }) {

  const org = Organization({
    name: "Acme Store",
    url: "https://store.acme.com",
    logo: "https://store.acme.com/logo.png"
  });

  const productSchema = {
    ...Product({
      name: product.name,
      description: product.description,
      url: `https://store.acme.com/products/${product.id}`,
      image: product.image,
      brandName: product.brand,
      sku: product.sku
    }),
    offers: {
      "@type": "Offer",
      price: product.price,
      priceCurrency: "USD",
      availability: product.inStock
        ? "https://schema.org/InStock"
        : "https://schema.org/OutOfStock"
    },
    aggregateRating: product.rating ? {
      "@type": "AggregateRating",
      ratingValue: product.rating.value,
      reviewCount: product.rating.count
    } : undefined
  };

  const breadcrumbs = BreadcrumbList({
    items: [
      { name: "Home", url: "https://store.acme.com" },
      { name: "Products", url: "https://store.acme.com/products" },
      { name: product.name, url: `https://store.acme.com/products/${product.id}` }
    ]
  });

  return (
    <>
      <Head>
        <title>{product.name} - Acme Store</title>
        <meta name="description" content={product.description} />
      </Head>

      <Schema data={[org, productSchema, breadcrumbs]} />

      <div className="product-page">
        {/* Product display */}
      </div>
    </>
  );
}

Best Practices

  • Always include Organization schema on key pages to establish site ownership

  • Use BreadcrumbList on deep pages to help search engines understand navigation

  • Keep Article dates updated when you modify content (use dateModified)

  • Test with Google’s Rich Results Test before deploying

  • Validate in CI/CD to catch errors early

Conclusion

Adding structured data doesn’t have to be painful. With Schema Sentry, you get:

  • ✅ Full TypeScript autocomplete and type safety

  • ✅ Compile-time validation

  • ✅ Zero runtime overhead

  • ✅ Works with both App Router and Pages Router

  • ✅ CI/CD integration for automated validation

Your future self—and your search rankings—will thank you.

Have you implemented structured data on your Next.js site? What challenges did you face? Share your experience in the comments !

Summary

Adding structured data to Next.js applications can significantly improve SEO, AI visibility, and search result quality. This guide demonstrated how Schema Sentry enables type-safe JSON-LD generation, supports both App Router and Pages Router, provides multiple schema builders, integrates with CI/CD pipelines, and ensures zero runtime overhead while preventing structured data errors from reaching production.