JSON-LD Implementation in Next.js
This documentation covers our JSON-LD (JavaScript Object Notation for Linked Data) implementation, which provides structured data for search engines to better understand our content. Our approach ensures that JSON-LD scripts are server-side rendered and injected early in the HTML, making them immediately available to search engine crawlers before JavaScript loads.
Overview
Our project implements two distinct JSON-LD loading strategies:
- Global JSON-LD: Site-wide structured data loaded in the root layout
- Page-specific JSON-LD: Dynamic structured data for individual pages
Key Advantage: Server-Side Prerendering
The most important aspect of our implementation is that JSON-LD scripts are prerendered on the server. This means:
- Search engines can read the structured data immediately when they crawl the page
- The data is present in the initial HTML response, not added later by JavaScript
- There’s no risk of crawlers missing the structured data due to JavaScript execution timing
- The
beforeInteractive
strategy ensures scripts are placed optimally in the document head
File Structure
- layout.tsx
- loadJsonFromIndex.ts
- getHomepageJsonLd.ts
Core Utilities
1. loadJsonFromIndex.ts
This utility creates React script elements from JSON-LD data arrays. It’s the primary method for injecting structured data into pages.
// app/lib/jsonld/loadJsonFromIndex.ts
import React from 'react';
export function loadJsonLdScripts(
jsonLdArray: any[],
idPrefix = 'jsonld'
): React.ReactElement[] {
return jsonLdArray.map((jsonLd, idx) =>
React.createElement('script', {
key: `${idPrefix}-${idx}`,
id: `${idPrefix}-${idx}`,
type: 'application/ld+json',
strategy: 'beforeInteractive', // Critical for early injection
dangerouslySetInnerHTML: { __html: JSON.stringify(jsonLd) },
})
);
}
Key Features:
- Uses
React.createElement
for build-time script generation beforeInteractive
strategy ensures scripts load before page hydration- Each script gets a unique ID for debugging
- Returns an array of React elements that can be directly rendered
2. getHomepageJsonLd.ts (Alternative Approach)
This utility reads JSON-LD files from the public directory at build time. It’s used for homepage-specific structured data.
// app/lib/jsonld/getHomepageJsonLd.ts
import fs from "fs";
import path from "path";
export function getHomepageJsonLd() {
// Reads .txt files containing JSON-LD from public/jsonld/homepage-jsonld/
// Validates JSON structure and returns parsed objects
// Provides detailed console warnings for debugging
}
Note: This approach uses Node.js filesystem APIs, so it only works at build time.
Implementation Patterns
Global JSON-LD (Site-wide)
Global structured data is loaded once in the root layout and appears on every page.
Step 1: Create JSON-LD data files
Create individual JSON files for each type of structured data:
// app/_data/jsonld/global/navigation.json
{
"@context": "https://schema.org",
"@type": "SiteNavigationElement",
"name": "Main Navigation",
"url": "https://ritoswap.com",
"hasPart": [
{
"@type": "WebPage",
"name": "Home",
"url": "https://ritoswap.com"
}
// ... other navigation items
]
}
Step 2: Create an index file to aggregate data
// app/_data/jsonld/global/index.ts
import navigation from './navigation.json';
const jsonLdData = [
navigation,
// Add other global JSON-LD files here
];
export default jsonLdData;
Step 3: Import and inject in layout.tsx
// app/layout.tsx
import { loadJsonLdScripts } from "@lib/jsonld/loadJsonFromIndex"
import globalJsonLdData from "./_data/jsonld/global"
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
{/* Inject global JSON-LD (site navigation) */}
{loadJsonLdScripts(globalJsonLdData, "global-jsonld")}
</head>
<body>
{/* ... rest of layout */}
</body>
</html>
)
}
Page-specific JSON-LD
Individual pages can have their own structured data following a similar pattern.
Step 1: Create page-specific JSON-LD directory
For each page route, create a jsonld
folder:
app/
terms/
page.tsx
jsonld/
breadcrumb.json
index.ts
Step 2: Define page-specific structured data
// app/terms/jsonld/breadcrumb.json
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "https://ritoswap.com"
},
{
"@type": "ListItem",
"position": 2,
"name": "Terms",
"item": "https://ritoswap.com/terms"
}
]
}
Step 3: Create page index file
// app/terms/jsonld/index.ts
import breadcrumb from './breadcrumb.json';
const jsonLdData = [
breadcrumb,
// Add other page-specific JSON-LD here
];
export default jsonLdData;
Step 4: Import in page component
// app/terms/page.tsx
import { loadJsonLdScripts } from "@lib/jsonld/loadJsonFromIndex"
import pageJsonLdData from "./jsonld"
export default function TermsPage() {
return (
<>
{loadJsonLdScripts(pageJsonLdData, "terms-jsonld")}
{/* Page content */}
</>
);
}
Import Types and Build Behavior
Import Type | When Executed | Use Case |
---|---|---|
JSON imports | Build time | Static structured data that doesn’t change |
loadJsonLdScripts | Build time (SSG) / Request time (SSR) | Converting JSON data to script elements |
getHomepageJsonLd | Build time only | Reading external JSON-LD files from filesystem |
Best Practices
-
Always validate JSON-LD: Use Google’s Rich Results Test to validate your structured data
-
Use TypeScript interfaces: Define types for your JSON-LD objects to catch errors at compile time
-
Keep data DRY: Reuse common patterns like breadcrumbs across pages
-
Monitor performance: While JSON-LD is lightweight, excessive structured data can impact page size
-
Test prerendering: View page source (not DevTools) to confirm JSON-LD appears in the initial HTML
Rendering Process Timeline
The beauty of our implementation is how early JSON-LD gets injected:
- Build Time: JSON files are imported and bundled
- Server Render:
loadJsonLdScripts
creates script elements - HTML Generation: Scripts are included in the
<head>
withbeforeInteractive
- Client Receives HTML: JSON-LD is already present before any JavaScript executes
- Search Engine Crawl: Structured data is immediately available
This approach guarantees that search engines will always see your structured data, regardless of their JavaScript execution capabilities.
Common JSON-LD Types
Here are the most commonly used structured data types in our application:
- Organization: Company information
- WebSite: Site-level data with search action
- SiteNavigationElement: Main navigation structure
- BreadcrumbList: Page hierarchy
- Product: For marketplace items
- FAQPage: Frequently asked questions
- Article: Blog posts and documentation
Troubleshooting
JSON-LD not appearing in page source
- Check that you’re importing and calling
loadJsonLdScripts
correctly - Ensure JSON files are valid (no trailing commas, proper quotes)
- Verify the import path is correct
Build errors with getHomepageJsonLd
- This function uses Node.js APIs and only works at build time
- Ensure the directory
public/jsonld/homepage-jsonld/
exists - Check that .txt files contain valid JSON
Scripts loading too late
- Confirm
strategy: 'beforeInteractive'
is set - Check that scripts are rendered in
<head>
or early in the body - Use View Source (not DevTools) to verify presence in initial HTML