DEV Community

Cover image for Routing and SEO Metadata in Next.js 13
Yoav Ganbar for Builder.io

Posted on • Originally published at builder.io on

Routing and SEO Metadata in Next.js 13

Written by Vishwas Gopinath

When it comes to building web applications, ensuring proper search engine optimization (SEO) and web shareability is crucial for increasing visibility and attracting users. In version 13, Next.js introduced the Metadata API, which allows you to define metadata for each page, ensuring accurate and relevant information is displayed when your pages are shared or indexed. In this blog post, we will explore how to leverage the Metadata API to enhance routing metadata.

Importance of routing metadata

When users navigate to different pages within your Next.js application, it's important to provide, at the very least, appropriate document titles and descriptions. By default, the root layout of a Next.js application created using create-next-app includes a metadata object in the layout.tsx file.

// app/layout.tsx
export const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}
Enter fullscreen mode Exit fullscreen mode

We will learn about the metadata object in a minute but it is quite clear that the metadata object contains a title property set to "Create Next App" and a description property set to “Generated by create next app”. The title property determines the document title displayed in the browser, while the description property corresponds to the meta tag in the head section of the document.

However, this predefined metadata might not provide the desired SEO benefits for individual pages in your application. The same document title and description are set regardless of the visited route.

If the landing page is titled “Builder.io - Visual Headless CMS”, it is carried across to the documentation page, the careers page, the pricing page, and every other page in the application. From the perspective of search engines, having appropriate document titles and descriptions for each page is essential for increasing the chances of users discovering your website.

Configuring metadata in relation to routing

There are two ways to configure metadata in a layout.tsx or page.tsx file:

  1. Export a static metadata object or
  2. Export a dynamic generateMetadata function

Static metadata

To define static metadata, export a Metadata object from a layout.tsx or page.tsx file. Here’s an example metadata object defined in app/page.tsx:

import { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Builder.io - Visual Headless CMS',
  description: 'Build digital experiences for any tech stack, visually.',
}

export default function Home() {
  // Home component
}
Enter fullscreen mode Exit fullscreen mode

When you navigate to localhost:3000 and inspect the head section of the document, you should see:

<title>Next.js</title>
<meta name="description" content="Build digital experiences for any tech stack, visually.">
Enter fullscreen mode Exit fullscreen mode

Dynamic metadata

Dynamic metadata depends on dynamic information, such as the current route parameters, external data, or metadata in parent segments.

To define dynamic metadata, export a generateMetadata function that returns a Metadata object from a layout.tsx or page.tsx file.

Here’s the generateMetadata function defined in app/products/[productId]/page.tsx:

import { Metadata } from "next";

type Props = {
  params: { productId: string };
};

export const generateMetadata = ({ params }: Props): Metadata => {
  return {
    title: `Product - ${params.productId}`,
  };
};

export default function ProductDetails() {
  // Product details component
}
Enter fullscreen mode Exit fullscreen mode

When you navigate to localhost:3000/products/camera and inspect the head section of the document, you should get:

<title>Product - Camera</title>
Enter fullscreen mode Exit fullscreen mode

And here’s an async generateMetadata function when we need external data:

import { Metadata } from "next";

type Props = {
  params: { productId: string };
};

export const generateMetadata = async (
  props: Props
): Promise<Metadata> => {
  const { params } = props
  const product = await fetchProductById(params.productId)
  return {
    title: product.title,
  };
};
Enter fullscreen mode Exit fullscreen mode

generateMetadata accepts a props parameter, which is an object containing the parameters of the current route. The props object contains two properties:

  1. params: An object that contains the dynamic route parameters from the root segment down to the segment where generateMetadata is called. For example, if the route is app/products/[productId]/page.js and the URL is /products/1, the params object would be { productId: '1' }.
  2. searchParams: An object that contains the current URL's search parameters. For instance, if the URL is /products?productId=1, the searchParams object would be { a: '1' }.

The presented example uses params, but you can also use searchParams with equal ease.

If the metadata doesn't depend on runtime information, it should be defined using the static metadata object rather than generateMetadata function.

Merging metadata

Metadata can be exported from both a layout.tsx file and a page.tsx file. Consequently, there may be multiple metadata exports for various segments within the same route.

In such cases, the metadata objects are combined to generate the final metadata output of a route. Duplicate keys are replaced starting from the root segment down to the segment closest to the final page.js segment.

Let's consider app/layout.tsx with the following metadata export:

import { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Builder.io - Visual Headless CMS',
  description: 'Build digital experiences for any tech stack, visually.',
}
Enter fullscreen mode Exit fullscreen mode

And app/blog/page.tsx with the following metadata export:

import { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Builder.io Blog'
}
Enter fullscreen mode Exit fullscreen mode

When you visit localhost:3000/blog, the resulting metadata output is as follows:

<title>Builder.io Blog</title>
<meta name="description" content="Build digital experiences for any tech stack, visually.">
Enter fullscreen mode Exit fullscreen mode

In this case, the title from the root layout is overwritten by the title in the page.tsx file within the blog folder. However, the description from the root layout is present in the merged metadata object, resulting in the blog page also containing the same description.

To configure metadata in relation to routing, there are a few important points to note:

  1. Metadata can be exported from both a layout.tsx file and a page.tsx file. Metadata defined in a layout applies to all pages within that layout, while metadata defined in a page only applies to that specific page.
  2. Metadata is evaluated in order, starting from the root segment down to the segment closest to the final page.js segment.
  3. Metadata objects exported from multiple segments within the same route are merged to form the final metadata output of a route. Duplicate keys are replaced based on their ordering. In other words, metadata in a page overwrites similar metadata in a layout.

By default, the metadata object is generated in the root layout when creating a Next.js app. However, you have the flexibility to define the metadata object in both the layout and the page, with the page metadata taking precedence over the layout metadata.

title field

Among the numerous metadata fields that can be specified, there is one field that holds significant importance, particularly from a routing perspective. This field is none other than the title field. Its primary purpose is to define the title of the document. It can be defined as a simple string or an optional template object.

String

The most direct way to set the title attribute is by using a string. For example, in the file layout.tsx or page.tsx, you can define the title as follows:

import { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Builder.io - Visual Headless CMS'
}
Enter fullscreen mode Exit fullscreen mode

This outputs the following HTML code in the head section:

<title>Builder.io - Visual Headless CMS</title>
Enter fullscreen mode Exit fullscreen mode

Template object

Next.js also allows you to define the title field using a template object, which provides more flexibility. For example, in the file layout.tsx or page.tsx, you can define the title as follows:

import { Metadata } from 'next'

export const metadata: Metadata = {
  title: {
    template: '...',
    default: '...',
    absolute: '...',
  },
}
Enter fullscreen mode Exit fullscreen mode
  • The template property in the title object can be used to add a prefix or a suffix to titles defined in child route segments.
  • The default property is used as a fallback title for child route segments that don't define a title.
  • The absolute property, if defined, overrides the template set in parent segments.

Default title

The title.default property comes in handy when you want to provide a fallback title for child route segments that don't explicitly define a title.

For example, in the app/layout.tsx file, you can set the default title as follows:

import { Metadata } from 'next'

export const metadata: Metadata = {
  title: {
    default: 'Builder.io - Visual Headless CMS',
  },
}
Enter fullscreen mode Exit fullscreen mode

If a child route segment, such as app/blog/page.tsx, doesn't have a title defined, it will fall back to the default title. So the output will be:

<title>'Builder.io - Visual Headless CMS'</title>
Enter fullscreen mode Exit fullscreen mode

Template title

To create dynamic titles by adding a prefix or a suffix, you can use the title.template property. This property applies to child route segments and not the segment in which it's defined.

In the app/layout.tsx file, define the metadata as follows:

import { Metadata } from 'next'

export const metadata: Metadata = {
  title: {
    template: '%s | Builder.io',
    default: 'Builder.io - Visual Headless CMS',
  },
}
Enter fullscreen mode Exit fullscreen mode

In the app/blog/page.tsx file, you can then define the title as usual:

import { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Blog',
}
Enter fullscreen mode Exit fullscreen mode

The output is:

<title>Blog | Builder.io</title>
Enter fullscreen mode Exit fullscreen mode

Absolute title

If you want to provide a title that completely ignores the title.template set in parent segments, you can use the title.absolute property.

In the app/layout.tsx file, define the metadata as follows:

import { Metadata } from 'next'

export const metadata: Metadata = {
  title: {
    template: '%s | Builder.io',
    default: 'Builder.io - Visual Headless CMS',
  },
}
Enter fullscreen mode Exit fullscreen mode

In the app/blog/page.tsx file, define the title as follows:

import { Metadata } from 'next'

export const metadata: Metadata = {
  title: {
    absolute: 'Blog',
  },
}
Enter fullscreen mode Exit fullscreen mode

The output will be:

<title>Blog</title>
Enter fullscreen mode Exit fullscreen mode

Let’s summarize the behavior of the title field.

In the layout.js file:

  • The title (as a string) and title.default are used to set a default title for child sections that don't have their own title. If a child section doesn't have a title, it will inherit the title template from the nearest parent section and use it as a basis.
  • The title.absolute is also used to set a default title for child sections, but it ignores the title template set in parent sections.

In the page.js file:

  • If a page doesn't have its own title, it will use the title of the nearest parent section.
  • The title (as a string) is used to define the title for the specific page. Similar to child sections, if a page doesn't have a title, it will inherit the title template from the nearest parent section and use it as a basis.
  • The title.absolute is used to define the title for the specific page, and it ignores the title template set in parent sections.
  • The title.template doesn't have any effect in page.js because a page is always the final section of a route and doesn't have any child sections.

Conclusion

Next.js provides powerful tools for managing routing metadata, enabling you to optimize your application for SEO and web shareability.

By leveraging the Metadata API, we can define metadata for each page, ensuring accurate and relevant information is displayed when pages are shared or indexed.

Configuring metadata in relation to routing is crucial for optimizing individual pages. We can use static metadata or dynamic metadata, depending on the specific needs of our application. Static metadata allows us to define metadata objects directly, while dynamic metadata relies on dynamic information.

Merging metadata enables us to combine metadata from different segments within the same route, resulting in the final metadata output. This allows for granular control over metadata at different levels of the application.

The title field can be defined as a string or a template object, providing flexibility in setting the document title. The template object allows for dynamic titles with prefixes or suffixes, default titles for segments without explicitly defined titles, and absolute titles that override parent templates.

By understanding and utilizing the Metadata API in Next.js, we can optimize our web applications for search engines, improve web shareability, and provide a better user experience.

Visually build with your components

Builder.io is a headless CMS that lets you drag and drop with your components right within your existing site.

Try it out Learn more

// Dynamically render your components
export function MyPage({ json }) {
  return <BuilderComponent content={json} />
}

registerComponents([MyHero, MyProducts])
Enter fullscreen mode Exit fullscreen mode
Read the full post on the Builder.io blog

Top comments (1)

Collapse
 
leandro_nnz profile image
Leandro Nuñez

Thanks!