Dynamic Metadata Generation and Data Fetching at Once in Next.js App Router
Nitish Kumar Singh
Feb 17, 2024Hello developers! In this blog post, we will explore how we can dynamically generate metadata and also fetch server component data by making a single fetch request in the page.js file in the Next.js App Router.
We all know that Next.js provides the generateMetadata
function for dynamic metadata generation. We can generate metadata by exporting the generateMetadata
function as shown in the code below:
export async function generateMetadata() {
let metaData = await getMetadata();
return metaData;
}
Where the getMetadata
function fetches data from the database, creates an object of Metadata with properties as desired, and returns it. You can explore and find the required list of properties (fields) of the Metadata object from the Next.js Docs. For example, the code below returns a metadata object with minimal fields:
const getMetadata = async ()=>{
const res = await fetch("URL to fetch your metadata from database");
if (res.ok) {
const data = await res.json();
return {
title:data.title,
description:data.desc,
alternates: {
canonical: 'https://code.nkslearning.com'+data.path
},
openGraph: {
title: data.title,
description: data.desc,
url: 'https://code.nkslearning.com'+data.path,
siteName: 'NKS CODING LEARNINGS',
images: [{url: data.img}],
type: 'article',
publishedTime:data.publishedTime,
authors:["NITISH KUMAR SINGH"]
}
};
}else{
// return default metadata or {} if default metadata exported from layout file
return {};
}
}
But what if we want to make only a single fetch request to fetch all data at once, meaning data that we fetch in both the server component and generateMetadata
function? In my case, I have stored all blog post data in a single document in the Appwrite database. So why make two requests to get data from the same place when I can get it all at once and also improve performance?
To achieve this, let's first understand what actually happens when we visit /a-route
in a Next.js app. Next.js first calls the server component function of that route, then just after a few milliseconds, it calls the generateMetadata
function, whether the server component has finished its work or not, and waits until generateMetadata
returns the metadata
object.
So I have tried some techniques to achieve what I want, and below is the code to bring metadata from the data fetched in the server component:
export async function generateMetadata() {
let metaData = await getMetadata();
return metaData;
}
var resolve;
const getMetadata = ()=>{
return new Promise((res)=>{
if (resolve && typeof resolve === "object") {
res(resolve);
}else resolve = res;
});
}
As mentioned above, Next.js first calls the server component function, then the generateMetadata
function. Therefore, in the getMetadata
function, I have checked whether the metadata object is already initialized in the resolve
variable (which almost never happens, but I added it for safety). If yes, then simply return it via the resolve
function of Promise. If not, then initialize the resolve
variable with the resolve (res) function of Promise.
Now let's handle the rest of the work in the server component, and below is the code to fetch data from the database and send it to the generateMetadata
function:
export default async function BlogPost({params}) {
let blogInfo = await fetchData(params);
const me = {
title:blogInfo.title,
description:blogInfo.desc,
alternates: {
canonical: blogInfo.path
},
openGraph: {
title: blogInfo.title,
description: blogInfo.desc,
url: 'https://code.nkslearning.com'+blogInfo.path,
siteName: 'NKS CODING LEARNINGS',
images: [{url: blogInfo.img}],
type: 'article',
publishedTime:blogInfo.$updatedAt,
authors:["NITISH KUMAR SINGH"]
}
};
if (resolve && typeof resolve === "function") resolve(me); else resolve = me;
return (<BlogPostPage blogInfo={blogInfo}/>);
}
Here, the fetchData
function fetches data from the database and returns it in JS object form. Then it creates a Metadata object, sends it to the generateMetadata
function, or stores it in the resolve variable and sends this fetched data to the child component.
This is the process to fetch the required data in a single request and handle dynamic metadata generation. I hope you understand it and find this post helpful. Happy Coding!