In a static HTML site, adding schema markup is trivial: paste the JSON-LD block in the <head> and you're done. In a Next.js or React application things get complicated: you have SSR, client hydration, the App Router with Server Components, the Pages Router with _document.tsx, and multiple pages with different schemas that must not interfere with each other. Doing it wrong can generate duplicate schema, hydration errors, or markup that Google doesn't detect.
Next.js App Router (Next.js 13+): the recommended approach
In the Next.js App Router, the correct place for schema is inside the page or layout component, using a <script> tag with dangerouslySetInnerHTML. Being a Server Component by default, the schema is rendered on the server and Google sees it in the initial HTML without needing to execute JavaScript.
The correct pattern is to define the schema object as a typed TypeScript constant and serialize it with JSON.stringify directly in the JSX:
- •Define the schema as a TypeScript object — typed, no risk of manual JSON syntax errors.
- •Use JSON.stringify(schema) inside dangerouslySetInnerHTML={{ __html: ... }}.
- •Place the <script> tag inside the page.tsx component of the corresponding route.
- •For global schemas (Organization, WebSite), place them in the root layout.tsx.
- •Don't use useEffect to inject the schema — it would lose SSR and Google might not detect it.
Next.js Pages Router: _document.tsx vs page component
In the Pages Router, you have two options depending on the schema type:
- •Global schemas (Organization, WebSite): add them in _document.tsx inside the <Head> — they render on all pages.
- •Page-specific schemas: add them in each page's component using the <Head> component from next/head with the <script> inside.
- •If you use next-seo or similar: many SEO libraries for Next.js include native support for JSON-LD — check the library's documentation before implementing manually.
Pure React (without Next.js): react-helmet or direct insertion
In a React SPA without SSR, schema can be added in two ways:
- •With react-helmet or react-helmet-async: allows managing the <head> declaratively per component, including <script> tags.
- •With useEffect + document.createElement: dynamically creates the script tag and adds it to the head. The problem is that the schema doesn't exist in the initial HTML — SPAs without SSR have limited schema visibility for Google.
- •Recommendation: if SEO is important, use SSR (Next.js, Remix) rather than a pure SPA.
Common schema errors in JavaScript apps
| Error | Cause | Solution |
|---|---|---|
| Duplicate schema | Schema in layout + schema in page for the same type | Centralize: global in layout, specific in page |
| Schema not detected by Google | Injected with client-side JS without SSR | Render on server or use SSR/SSG |
| Hydration error | Schema generated dynamically with data that differs between server and client | Generate schema with static data or ensure the initial state is the same |
| Invalid JSON in production | Data with special characters not escaped in JSON.stringify | Use JSON.stringify — never build the JSON as a string with interpolation |
How to generate the JSON-LD for your Next.js app
iRankly's AI Schema Generator produces the JSON object ready to use. Copy it, paste it as a constant in your Next.js component, and use JSON.stringify to serialize it in the script tag. The generator includes all Google-recommended fields for the selected schema type.
Prueba la herramienta gratis
AI Schema GeneratorAnaliza tus URLs con {tool} de iRankly. Sin registro, sin tarjeta.
Verify schema with iRankly's Schema Validator
Once implemented, verify that Google correctly detects the schema with iRankly's Schema Validator: enter the URL of the already-published page and check that the schema appears correctly parsed without errors in required fields.