The gotcha of generateMetadata in Next.js

There are multiple ways to configure metadata for a page in Next.js. One of the ways is to use the generateMetadata function which allows you to dynamically set metadata properties for a page (or a layout). This is great for SEO as it allows your page routes to provide more meaningful metadata context to search engines about a dynamic route.

Consider the example:

export const generateMetadata = async ({ params }) => {
  // This could take 1ms or 1 minute
  const data = await someLongFetch(`/data`);
  return {
    title: data.title,
    description: data.description,
  };
};

The problem with generateMetadata is one key note from the Next docs:

Next.js will wait for data fetching inside generateMetadata to complete before streaming UI to the client. This guarantees the first part of a streamed response includes <head> tags.

Why does this matter?

This means that if you have a slow data fetching operation inside generateMetadata, your page will take longer to get to the render. There is this issue that calls this exact problem out. This can be a blocker for teams that require dynamic metadata for their pages but also need to optimize for performance with fast page renders. You want your metadata to be descriptive and accurate, but you also want your page to load quickly. Currently, as of Next 15, there is no good native solution.

So, what can I do?

Optimizations to save time saving time can be done in different ways.

Optimize the fetch: If dynamic metadata is required, your first attempt should be to optimize the data fetching. Making a request that requires less processing or has less data to return will naturally be faster. If you are unable to optimize the fetch from the source, you can leverage other optimizations from Next.js and React to help. You can leverage the Next.js data cache to cache the reponse for future requests. You would still have the initial slow fetch for the first request, but subsequent requests would be faster since the data would be provided from the cache. An example would be displaying a product from a complex database query. Loading the entire product data could take time, but if you cache the data on the first request, the subsequent requests would be much faster.

Unblock the render: You can also use a hybrid approach where you set metadata properties that are static or can be fetched quickly with generateMetadata and then fetch the rest of the metadata properties with a client-side components or suspended server components that pass the data to client-side components. This way, you can provide a fast initial load time for your page and offload the fetching of the rest of the metadata properties to the streaming after the initial page is sent over. This is beneficial where you want to give your user dynamic metadata but is not needed for SEO purposes. An example of this would be a page for a current offer or promotion only available to an authenticated user. The page itself can be rendered from the server, but it is not necessarily a page that you'd want indexed by search engines. You can take this even farther with Next.js concepts such as Incremental Static Regeneration (ISR) and Partial Pre-rendering to optimize even further!

Go static: Finally, if the situation allows, you can leverage static pages to be built if you know all of your dynamic routes. You can tell Next.js to build static pages for all of your dynamic routes and set the proper metadata properties for those pages. This would generate these at build time and would not require any data fetching at runtime. This is the most performant solution but is not always possible if you have a large number of dynamic routes or if the data is constantly changing.

In the end, Next.js does not have a full-proof solution for this issue and in my opinion can be a blocker for some use-cases, but there are ways to compromise and optimize your page to create the best user experience.