Static and Dynamic Metadata in Next.js

Static and Dynamic Metadata in Next.js

Metadata tags are an important part of SEO, and in this tutorial we'll be going over how to implement them in Next.js.

Let's say we are creating a blog page. On the landing page, we want the title meta tag to be "Super Awesome Blog". On the page for an article titled "How to set up dynamic metadata in Next.js", we want the title to be "How to set up dynamic metadata in Next.js". We want this behavior to occur for every different blog article's page, changing the title, description, and even OG and Twitter fields depending on the article.

There are a couple approaches to metadata in Next.js, and we'll use each of them in this example.

Static Metadata

For the home page of the blog, say we want the title to be "Super Awesome Blog" and the description to be "This is a super awesome blog, you should read it". For the home page, we can define the metadata statically by exporting a Metadata object from the root layout.

import { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Super Awesome Blog',
  description: 'This is a super awesome blog, you should read it',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
          {children}
      </body>
    </html>
  )
}

You'll notice that when you create a new Next.js project, the root layout will likely already have this. But we can do a bit more.

The Metadata object allows us to define other metadata tags as well, such as the favicon, OpenGraph fields, and Twitter fields. Here's some examples:

export const metadata: Metadata = {
  title: "Super Awesome Blog",
  description: "This is a super awesome blog, you should read it",
  robots: {
    follow: true,
    index: true,
  },
  openGraph: {
    url: 'https://blog.com',
    title: "Super Awesome Blog",
    description: "This is a super awesome blog, you should read it",
    images: [
      {
        url: `https://blog.com`,
      }
    ]
  },
  twitter: {
    card: 'summary_large_image',
    creator: '@blog',
    title: 'Super Awesome Blog',
    description: 'This is a super awesome blog, you should read it',
    site: '@blog',
    images: ['https://blog.com'],
  },
  category: 'technology',
  authors: [{ name: 'Jeremy Quinto'} ],
  other: {
    copyright: 'Jeremy Quinto',
    'revisit-after': '7 days',
    distribution: 'California',
    rating: 'general',
    language: 'EN',
    city: 'Los Angeles',
  }
};

Here, we define metadata tags such as robots, OpenGraph, Twitter, and more. You can also define your own fields using the other field.

Next.js automatically puts these tags in the <head> of the page.

Title Templates

Let's say, for the blog website, we want the title of the home page to be "Blog", and the title of any individual post to be "Blog | {title of post}". With the Metadata object, we can use a template for the title.

Here, we are using a template in root layout.tsx.

export const metadata: Metadata = {
  title: {
    template: "Blog | %s",
    default: 'Blog',
  },
  description: "This is a super awesome blog, you should read it",
};

This creates a template that will be used by the children routes.

Now, in the page.tsx for the article (which you can put inside a [articleName] folder, for example, you can do:

import { Metadata } from 'next'

export const metadata: Metadata = {
    title: "Title of article",
    description: "This is a sick post, you should read it"
}

const BlogPage = () => {
    return (
        <div>
            {/* Article */}
        </div>
    )
}

export default BlogPage

Now, you can see the title of that blog page will be "Blog | Title of article".

But now we need to make the title of the blog page be dynamic, since we obviously don't want it to say "Blog | Title of article" for every article.


Dynamic Metadata

To implement dynamic metadata in Next.js, we can use the generateMetadata() function. This function can make API calls to fetch data, and will return a Metadata object.

import { Metadata } from 'next'

export async function generateMetadata({
    params,
  }: {
    params: { slug: string }
  }): Promise<Metadata> {
    const blogPost = await getBlogPost(slug)

    const fullUrl = `https://blog.com/${fullSlug}`

    return {
      title: blogPost.title,
      description: blogPost.metaDescription,
      openGraph: {
        title: blogPost.title,
        description: blogPost.metaDescription,
        url: fullUrl,
        type: 'article',
        publishedTime: blogPost.publishDate,
        authors: [blogPost.defaultAuthor],
      },
      twitter: {
        title: blogPost.title,
        description: blogPost.metaDescription,
        card: 'summary_large_image',
        creator: '',
        site: config.userTwitter ? config.userTwitter : '',
      },
      other: {
        image: blogPost.imgUrl,
      },
    }
}

In the generateMetadata function, we are fetching the data for the current blog post (using the slug to know which one to fetch). We use a server action called getBlogPost, which, in my case, makes a GraphQL query to Contentful for the blog post.

Then, we populate the title, description, OpenGraph, and Twitter meta fields. These will replace the corresponding fields that we defined in the root layout (the title will be templated out).

This will be good for the SEO of the individual article, and will give it a good social preview that is separate from the landing page of the website!

It's great to note that any duplicate fetch requests for the same data in Next.js are automatically memoized across generateMetadata, generateStaticParams, Layouts, Pages, and Server Components. This means that when you use the getBlogPost function again in the actual component/page, Next.js doesn't have to call it again.

It's also good to note that Next.js will wait for the data fetching inside generateMetadata to complete before streaming UI to the client, which guarantees that the first part of a streamed response includes the correct <head> tags.


Thanks for reading!

Thanks for reading this article! I hope you learned something about Next.js while reading this. In the next article, we will go over more SEO techniques in Next.js, such as setting JSON-LD, sitemaps, and RSS feeds.