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.
Configuring metadata in relation to routing
There are two ways to configure metadata in a layout.tsx
or page.tsx
file:
- Export a static
metadata
object or - 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
}
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
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:
params
: An object that contains the dynamic route parameters from the root segment down to the segment wheregenerateMetadata
is called. For example, if the route isapp/products/[productId]/page.js
and the URL is/products/1
, theparams
object would be{ productId: '1' }
.searchParams
: An object that contains the current URL's search parameters. For instance, if the URL is/products?productId=1
, thesearchParams
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.',
}
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:
- Metadata can be exported from both a
layout.tsx
file and apage.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. - Metadata is evaluated in order, starting from the root segment down to the segment closest to the final
page.js
segment. - 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'
}
This outputs the following HTML code in the head
section:
<title>Builder.io - Visual Headless CMS</title>
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: '...',
},
}
- The
template
property in thetitle
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 thetemplate
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',
},
}
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>
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',
},
}
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>
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',
},
}
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) andtitle.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 inpage.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.
Introducing Visual Copilot: convert Figma designs to high quality code in a single click.