Dynamic Metadata Generation and Data Fetching at Once in Next.js App Router

Nitish Kumar Singh

Feb 17, 2024

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

Metadata generation in Next.js

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!

Published on Feb 17, 2024
Comments (undefined)

Read More