Learn how to ship impactful customer journeys with Builder

Announcing Visual Copilot - Figma to production in half the time

Builder.io logo
Contact Sales
Platform
Developers
Contact Sales

Blog

Home

Resources

Blog

Forum

Github

Login

Signup

×

Visual CMS

Drag-and-drop visual editor and headless CMS for any tech stack

Theme Studio for Shopify

Build and optimize your Shopify-hosted storefront, no coding required

Resources

Blog

Get StartedLogin

‹ Back to blog

Web Development

Routing and SEO Metadata in Next.js 13

June 21, 2023

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',
}

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.

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

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
}

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.">

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
}

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

<title>Product - Camera</title>

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,
  };
};

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.

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.',
}

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

import { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Builder.io Blog'
}

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.">

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.

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.

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'
}

This outputs the following HTML code in the head section:

<title>Builder.io - Visual Headless CMS</title>

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: '...',
  },
}
  • 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.

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',
  },
}

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>

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',
  },
}

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',
}

The output is:

<title>Blog | Builder.io</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',
  },
}

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

import { Metadata } from 'next'

export const metadata: Metadata = {
  title: {
    absolute: 'Blog',
  },
}

The output will be:

<title>Blog</title>

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.

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.

Introducing Visual Copilot: convert Figma designs to high quality code in a single click.

Try Visual Copilot

Share

Twitter
LinkedIn
Facebook
Hand written text that says "A drag and drop headless CMS?"

Introducing Visual Copilot:

A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot
Newsletter

Like our content?

Join Our Newsletter

Continue Reading
AI8 MIN
How to Build Reliable AI Tools
November 15, 2024
Web Design11 MIN
Design Smarter with Figma Auto Layout
November 13, 2024
Web Development10 MIN
A Guide to Server-Side Rendering
November 12, 2024