6

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.

6
  • Most likely this feature is not of high priority for Next.js team because JSON-LD data can be rendered anywhere in the page body - stackoverflow.com/questions/28687653/… ; and in fact that’s what Next.js documentation recommends - nextjs.org/docs/app/building-your-application/optimizing/… Jul 20, 2023 at 21:51
  • @IgorDanchenko Thank you! I didn't realize it could go anywhere inside the body too. All of the documentation I've seen on schemas has always said put it in the <head>. That's super helpful.
    – andrilla
    Jul 22, 2023 at 14:48
  • 1
    Not everything can go anywhere. It should have been a priority before shoving this unfinished version down our throats. Aug 23, 2023 at 12:09
  • @MarekMaurizio If you have a better solution, feel free to share.
    – andrilla
    Aug 25, 2023 at 13:12
  • @andrilla gladly, at the moment I use the pages router. Aug 28, 2023 at 7:23

2 Answers 2

7

Official 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>
  )
}
2
  • Should I write the actual page components in the <section> tag as well? What is this <section> for, is it related to json-ld? Also, does this ld+json script actually get inserted into the head? Because reading the code gives an impression that the script is inserted in the document body. Apr 15 at 11:37
  • 1
    @OlegYablokov The <section> tag is simply the tag I'm using for my wrapping element in this case. It is not relevant to the JSON-LD. The tag you would use is only relevant to semantic HTML. If you read the explanation of this code, you would see that it renders the <script> tag where it's placed in your component—not in the <head>. Although, it's recommended to put the JSON-LD in the <head>, it's not necessary. I believe it's still important that it precedes the data it's referencing. Read Next.js' documentation for more info.
    – andrilla
    Apr 16 at 14:53
1

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

1
  • 2
    This does not answer the question. This only works in the /pages directory. The question is specifically about the /app directory. You cannot use the next/head component in the /app directory, as shown here: nextjs.org/docs/app/api-reference/components. There is no <Head> equivalent.
    – andrilla
    Oct 2, 2023 at 13:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.