tl;dr
Next.js 13's /app
router's layout
and page
routing changes how we add content to the <head>
. How can I add a schema script to each page? Next.js will automatically compile <head>
tags placed in any layout
or page
into a single <head>
.
The goal
Just like in any website with great SEO, I want to include a schema script in the head of each page.
The history
Normally, this would be as easy as writing it in the <head>
like so:
<!-- index.html -->
<head>
<script type="application/ld+json">
{
"@context": ";,
// ... the rest
}
</script>
</head>
Then, with the Next.js /pages
directory it was a little different. It always felt weird to me to have to use the dangerouslySetInnerHTML
attribute, but at least it worked.
// index.tsx
import Head from 'next/head'
export default function Page() {
return (
<Head>
<script id="schema" type="application/ld+json" dangerouslySetInnerHTML={{__html: `
{
"@context": ";,
// ... the rest
}
`}} />
</Head>
)
}
The issue
Now with the introduction of the /app
router, we've got great, new, simplified ways to set metadata, without having to directly import the <head>
through next/head
. In fact, the next/head
component shouldn't be used in the /app
router.
So the question becomes...
How do we access the <head>
on a page-per-page basis?
I was hoping the Next.js team would have already thought about this, and added schema to the new metadata
variable, or even it's own variable, but they don't seem to have plans to do this as far as I can tell.
I tried adding a <head>
to the page
and unfortunately, it doesn't properly merge into the <head>
. So far the only thing I can thing to do would be to add the schema to each layout
, but what a hassle that would be to have an individual layout for each page.
Any ideas are welcome.
tl;dr
Next.js 13's /app
router's layout
and page
routing changes how we add content to the <head>
. How can I add a schema script to each page? Next.js will automatically compile <head>
tags placed in any layout
or page
into a single <head>
.
The goal
Just like in any website with great SEO, I want to include a schema script in the head of each page.
The history
Normally, this would be as easy as writing it in the <head>
like so:
<!-- index.html -->
<head>
<script type="application/ld+json">
{
"@context": "https://schema.org",
// ... the rest
}
</script>
</head>
Then, with the Next.js /pages
directory it was a little different. It always felt weird to me to have to use the dangerouslySetInnerHTML
attribute, but at least it worked.
// index.tsx
import Head from 'next/head'
export default function Page() {
return (
<Head>
<script id="schema" type="application/ld+json" dangerouslySetInnerHTML={{__html: `
{
"@context": "https://schema.org",
// ... the rest
}
`}} />
</Head>
)
}
The issue
Now with the introduction of the /app
router, we've got great, new, simplified ways to set metadata, without having to directly import the <head>
through next/head
. In fact, the next/head
component shouldn't be used in the /app
router.
So the question becomes...
How do we access the <head>
on a page-per-page basis?
I was hoping the Next.js team would have already thought about this, and added schema to the new metadata
variable, or even it's own variable, but they don't seem to have plans to do this as far as I can tell.
I tried adding a <head>
to the page
and unfortunately, it doesn't properly merge into the <head>
. So far the only thing I can thing to do would be to add the schema to each layout
, but what a hassle that would be to have an individual layout for each page.
Any ideas are welcome.
Share Improve this question edited Jul 20, 2023 at 19:44 andrilla asked Jul 20, 2023 at 16:36 andrillaandrilla 7211 gold badge9 silver badges25 bronze badges 6 | Show 1 more comment2 Answers
Reset to default 16Official Next.js Answer
The Next.js team has official documentation on this at Metadata – JSON-LD. (Putting the schema in the <head>
is not required, as demonstrated in the code)
Code Example
Using a JS object and a <script>
tag, we can add the JSON-LD on any page.
// page.tsx
export default async function Page({ params }) {
const product = await getProduct(params.id)
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
image: product.image,
description: product.description,
}
return (
<section>
{/* Add JSON-LD to your page */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
{/* ... */}
</section>
)
}
you might have the answer already but for all of us that might be looking for a solution to the same problem:
import Head from 'next/head';
function ProductPage() {
function addProductJsonLd() {
return {
__html: `{
"@context": "https://schema.org/",
"@type": "Product",
"name": "Executive Anvil",
"image": [
"https://example.com/photos/1x1/photo.jpg",
"https://example.com/photos/4x3/photo.jpg",
"https://example.com/photos/16x9/photo.jpg"
],
"description": "Sleeker than ACME's Classic Anvil, the Executive Anvil is perfect for the business traveler looking for something to drop from a height.",
"sku": "0446310786",
"mpn": "925872",
"brand": {
"@type": "Brand",
"name": "ACME"
},
"review": {
"@type": "Review",
"reviewRating": {
"@type": "Rating",
"ratingValue": "4",
"bestRating": "5"
},
"author": {
"@type": "Person",
"name": "Fred Benson"
}
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.4",
"reviewCount": "89"
},
"offers": {
"@type": "Offer",
"url": "https://example.com/anvil",
"priceCurrency": "USD",
"price": "119.99",
"priceValidUntil": "2020-11-20",
"itemCondition": "https://schema.org/UsedCondition",
"availability": "https://schema.org/InStock"
}
}
`,
};
}
return (
<div>
<Head>
<title>My Product</title>
<meta
name="description"
content="Super product with free shipping."
key="desc"
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={addProductJsonLd()}
key="product-jsonld"
/>
</Head>
<h1>My Product</h1>
<p>Super product for sale.</p>
</div>
);
}
export default ProductPage;
Here is the link with more info in the next.js documentation! https://nextjs.org/learn/seo/rendering-and-ranking/metadata
<head>
. That's super helpful. – andrilla Commented Jul 22, 2023 at 14:48