afaqrashid.com  ·  portfolio · 2026 ed.

Next.js Metadata Not Working? 8 Causes I Tested

When Next.js metadata isn't working in the App Router, it's usually one of eight things. Three of them stop the build with a clear error. The other five fail without a word, and those are the ones that cost time.

I tested every case below on a fresh Next.js 16.4.4 app with next build and next start, then read the HTML each page returned. The root layout in the test app looked like this:

// app/layout.tsx
export const metadata: Metadata = {
  title: { template: "%s | Site", default: "Site" },
  description: "layout desc",
};

1. You used next/head in the App Router

next/head is the Pages Router way to set tags. In the App Router it does nothing. My page rendered a Head with its own title and description, and the HTML still had the layout's "Site" and "layout desc". No error, no warning.

The fix is to remove the Head and export metadata, or generateMetadata, from the page:

// app/pricing/page.tsx
import type { Metadata } from "next";

export const metadata: Metadata = {
  title: "Pricing",
  description: "Plans and prices.",
};

2. The metadata export is in a component, not a page or layout

Next.js only reads metadata from layout and page files. I exported metadata from components/Seo.tsx and rendered that component on a page. Next.js ignored it, and the page kept the layout's title.

Move the export into the page.tsx or layout.tsx for that route. A shared helper that returns a Metadata object is fine, as long as the page is the file that exports it.

3. The title template doesn't apply to your home page

With the layout above, app/page.tsx set its title to "Home". I expected "Home | Site" and got just "Home".

That's documented: "title.template applies to child route segments and not the segment it's defined in." app/page.tsx sits in the same segment as app/layout.tsx, so the template skips it. Every other page, like app/about/page.tsx, gets the template.

I tried the same thing one level down. A template in app/f/layout.tsx didn't apply to app/f/page.tsx, which got the root template instead. If you want the suffix on the home page, write the full title there:

// app/page.tsx
export const metadata: Metadata = {
  title: { absolute: "Home | Site" },
};

title.absolute also works the other way, when a page should ignore the template. A page with title: { absolute: "Absolute C" } got exactly that title.

4. You put a title tag in the JSX

React 19 lets you render title and meta tags inside a component, and they end up in the head. Mixed with Next.js metadata, you get both. My page with a JSX title came out with two title tags and two meta descriptions:

<title>Site</title>
<meta name="description" content="layout desc"/>
<title>React title D</title>
<meta name="description" content="react desc D"/>

The layout's tags came first. Pick one way to set metadata per site; in the App Router that should be the metadata API.

5. Your generateMetadata tags are in the body

Since Next.js 15.2, generateMetadata can stream. On a dynamic page, Next.js sends the page first and adds the tags when generateMetadata finishes. I made a dynamic route with a 300 ms delay in generateMetadata and requested it with different user agents:

Chrome       title in <body>
Googlebot    title in <body>
Bingbot      title in <head>
Twitterbot   title in <head>

That's how it's meant to work. The docs say Googlebot reads tags in the body because it runs JavaScript, and bots that can't run JavaScript get the tags in the head. Next.js decides which is which from the user agent.

It bites when a tool you depend on reads only the head and isn't on Next.js's list. You can add it with htmlLimitedBots in next.config, or turn streaming off for everyone, which makes slow pages slower to start:

// next.config.ts
const config: NextConfig = {
  htmlLimitedBots: /.*/,
};

6. A page's openGraph removed the layout's

Metadata merges shallowly. If a page sets openGraph, the layout's whole openGraph object is replaced, including the share image. I wrote this one up in full in the Open Graph post linked below.

7. The page is a client component

This one does stop the build. With "use client" at the top of a page that exports metadata, I got:

You are attempting to export "metadata" from a component marked with "use client", which is disallowed.

Keep page.tsx as a server component that exports metadata, and move the interactive part into its own file with "use client".

8. You exported both metadata and generateMetadata

Also a build error:

"metadata" and "generateMetadata" cannot be exported at the same time, please keep one of them.

Use metadata for fixed values and generateMetadata when the values depend on params or data.

How to check what a page really sends

Don't trust the Elements panel: it shows the page after JavaScript has run. Check the raw HTML instead, as a crawler gets it:

curl -s https://example.com/pricing | grep -oE '<title>[^<]*</title>|<meta name="description"[^>]*>'

Or paste the page source into the metadata checker below. It reads the title, description, canonical, Open Graph tags and favicon, and flags tags that ended up in the body.

Related

Sources