Why Your Next.js Meta Tags Aren't Working (And How to Fix Them)

Why Your Next.js Meta Tags Aren't Working (And How to Fix Them)

Summary

  • The primary reason Next.js meta tags fail is the discrepancy between what crawlers see (initial server-rendered HTML) and what developers see in the browser's inspector. Always use "View Page Source" as your ground truth for debugging.
  • In the App Router, solve meta tag issues by using the official Metadata API. Export a metadata object for static content and the generateMetadata function for dynamic pages.
  • For the Pages Router, prevent duplicate tags by adding a unique key prop to each tag within next/head, and ensure crawlers see your tags by fetching data server-side with getServerSideProps or getStaticProps.
  • Flawless technical SEO is crucial for getting discovered. Synscribe's full-stack engineering team offers technical audits and implementation to ensure your site is perfectly optimized for search.

You've spent hours meticulously setting up your Next.js project with the perfect meta tags. You've added your og:image and crafted a compelling description that would make any SEO expert proud. But when you share the link on Twitter or Facebook, the preview is completely broken. Or maybe you've inspected your page only to discover duplicate <meta> tags appearing after navigation.

If you're nodding along in frustration, you're not alone. These are common headaches for Next.js developers, especially when everything looks perfect on localhost but mysteriously fails once deployed to Vercel.

Frustrated by meta tag issues? Synscribe's full-stack engineering team can audit and fix your Next.js SEO problems for optimal search visibility. Book a Technical Audit

"On localhost the meta tags are showing but when I deploy my app to vercel, the og meta tags are not displaying in page source or in the inspector console." - Reddit user

This disconnect between what you expect and what actually happens stems from a fundamental misunderstanding of how Next.js handles meta tags in its different rendering environments. Let's demystify this once and for all.

Why Your Meta Tags Are Breaking: The Core Problem

The root of most Next.js meta tag issues lies in a critical distinction that's often overlooked: what search engines see versus what humans see in the browser.

Search engine crawlers, social media bots, and tools like the Facebook Sharing Debugger don't typically execute JavaScript. They only read the initial HTML document that your server sends. This creates a fundamental rule for debugging:

Always use "View Page Source" (Ctrl+U or Cmd+Opt+U) to see what crawlers see.

This differs dramatically from using "Inspect Element," which shows you the live DOM after JavaScript has run. If your tags look perfect in the inspector but are missing from the page source, crawlers won't see them either.

Why So Many Different Meta Tags?

Another common point of confusion is the necessity of multiple meta tag types. Is it really necessary to have standard HTML meta tags, Open Graph tags, AND Twitter Cards?

Yes, and here's why:

  1. Standard Meta Tags: These are the classic HTML tags like <meta name="description"> that search engines like Google use to understand your page content.
<meta name="description" content="A guide to fixing meta tag issues in Next.js" />
  1. Open Graph (OG) Tags: Developed by Facebook, these tags (prefixed with og:) control how your content appears when shared on Facebook, LinkedIn, and many other platforms.
<meta property="og:title" content="Why Your Next.js Meta Tags Aren't Working" />
<meta property="og:image" content="https://yourdomain.com/og-image.jpg" />
  1. Twitter Cards: Specific tags for Twitter's rich preview cards, though Twitter will fall back to OG tags if these are missing.
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Fix Your Next.js Meta Tags" />

Each set serves a different purpose, and omitting any can lead to poor presentation on different platforms. Now let's dive into solutions for both the App Router and Pages Router.

The Modern Fix: Meta Tags in the App Router

If you're using Next.js 13+ with the App Router, you have access to the powerful Metadata API - the officially recommended approach for managing your <head> elements.

Static Metadata

For tags that don't change between renders, export a metadata object directly from your layout or page file:

// app/page.tsx or app/blog/page.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'My Next.js Blog',
  description: 'A blog built with Next.js and the App Router',
  openGraph: {
    title: 'My Next.js Blog',
    description: 'A blog built with Next.js and the App Router',
    images: ['/og-image.jpg'],
  },
  twitter: {
    card: 'summary_large_image',
    title: 'My Next.js Blog',
    description: 'A blog built with Next.js and the App Router',
    images: ['/og-image.jpg'],
  }
}

Dynamic Metadata

For content that depends on dynamic data (like a blog post title based on its slug), use the generateMetadata function:

// app/blog/[slug]/page.tsx
import type { Metadata, ResolvingMetadata } from 'next'

type Props = {
  params: { slug: string }
}

export async function generateMetadata(
  { params }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  // Fetch blog post data
  const post = await fetch(`https://api.example.com/blog/${params.slug}`).then((res) => res.json())

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [post.ogImage || '/default-og.jpg'],
    }
  }
}

Common App Router Metadata Pitfalls

Metadata Object Placement

A common question among developers is: "Is it okay to have metadata in both layout.jsx and index page.jsx?"

Yes! Next.js uses a powerful inheritance model for metadata. It reads the metadata object from the root layout, then merges it with metadata from nested layouts and pages, with the most specific values taking precedence.

This means you can define common metadata in your root layout and override specific fields in individual pages.

Streaming Limitations

While the App Router supports streaming (progressively rendering parts of your page), crawlers need metadata in the initial HTML chunk. Fortunately, Next.js automatically defers streaming until metadata is generated for known crawler user-agents.

OG Image Generation

For dynamic Open Graph images, the App Router provides the ImageResponse constructor from next/server:

// app/og/route.tsx
import { ImageResponse } from 'next/server'
 
export const runtime = 'edge'
 
export async function GET() {
  return new ImageResponse(
    (
      <div
        style={{
          display: 'flex',
          background: 'black',
          width: '100%',
          height: '100%',
        }}
      >
        <h1 style={{ color: 'white' }}>Dynamic OG Image</h1>
      </div>
    ),
    {
      width: 1200,
      height: 630,
    }
  )
}

You can test your dynamic OG images using Vercel's OG Playground.

The Classic Fix: Meta Tags in the Pages Router

If you're using the Pages Router with <Head> from next/head, here are the most common issues and their solutions:

Problem 1: Duplicate Tags During Navigation

Symptom: When navigating between pages, you see duplicate meta tags in the inspector.

Cause: When you navigate, Next.js might not unmount old tags before mounting new ones.

Solution: Add a unique key prop to each <meta> and <link> tag within <Head>:

// pages/about.js
import Head from 'next/head'

function AboutPage() {
  return (
    <>
      <Head>
        <title key="title">About Us</title>
        <meta name="description" content="About our company" key="desc" />
        <meta property="og:title" content="About Us" key="og-title" />
      </Head>
      {/* Page content */}
    </>
  )
}

The key props ensure React knows to replace these elements rather than adding new ones during client-side navigation.

Problem 2: Conflicts with _document.js

Symptom: Meta tags appear duplicated or aren't being updated properly.

Cause: Using <Head> from next/document and <Head> from next/head for the same tags.

Solution: Keep your _document.js minimal. It should only contain tags that apply to every page and never change:

// pages/_document.js - KEEP THIS CLEAN
import Document, { Html, Head, Main, NextScript } from 'next/document'

class MyDocument extends Document {
  render() {
    return (
      <Html lang="en">
        <Head>
          {/* Only put unchanging, global tags here */}
          <meta charSet="utf-8" />
          <link rel="icon" href="/favicon.ico" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

export default MyDocument

All other meta tags—titles, descriptions, social tags—should go in page components using import Head from 'next/head'.

Problem 3: Meta Tags Not Visible to Crawlers

Symptom: Tags look perfect in the browser inspector but are missing when you "View Page Source" or in social media previews.

Cause: You're likely fetching data on the client side (e.g., in a useEffect hook) before setting meta tags, but crawlers only see the server-rendered HTML.

Solution: Fetch data on the server using getServerSideProps or getStaticProps:

// pages/posts/[id].js
import Head from 'next/head'

function Post({ post }) {
  return (
    <>
      <Head>
        <title key="title">{post.title}</title>
        <meta name="description" content={post.excerpt} key="desc" />
        <meta property="og:title" content={post.title} key="og-title" />
        <meta property="og:image" content={post.ogImage} key="og-image" />
      </Head>
      {/* Post content */}
    </>
  )
}

export async function getServerSideProps(context) {
  const res = await fetch(`https://api.example.com/posts/${context.params.id}`)
  const post = await res.json()
  return { props: { post } }
}

export default Post

This ensures your meta tags are included in the initial HTML that crawlers receive.

Your Ultimate Meta Tag Debugging Toolkit

When your meta tags aren't working as expected, follow this debugging process:

1. View Page Source is Your Ground Truth

The most critical step is to check what's in your page source (Ctrl+U or Cmd+Opt+U in most browsers). If your tags aren't here, search engines and social media bots can't see them.

2. Use External Validators

These tools act like crawlers and show you exactly what they see:

3. Check for Duplicate Tags

Use browser dev tools to inspect your page after client-side navigation. If you see duplicates, you likely need to add key props to your meta tags in the Pages Router.

Struggling with technical SEO? Let Synscribe's engineers implement flawless meta tags and technical optimizations to boost your search rankings. Get Expert Help

Conclusion: Mastering Meta Tags in Next.js

Successfully implementing meta tags in Next.js comes down to understanding the rendering pipeline and how crawlers interact with your site.

For the App Router, trust the Metadata API. Use the metadata object for static content and generateMetadata for dynamic pages.

For the Pages Router, use next/head, always add key props to prevent duplication, keep _document.js clean, and fetch data server-side with getServerSideProps or getStaticProps.

By following these guidelines and using the debugging toolkit, you can ensure your Next.js application is perfectly optimized for search engines and social media platforms. The key is always remembering the distinction between what crawlers see (the page source) and what users see (the rendered DOM).

Mastering meta tags is a core skill for technical SEO. With the right approach, you can ensure your carefully crafted content looks great everywhere it's shared, improving both discoverability and user engagement.

Tags:
Published on January 27, 2026

Dominate ChatGPT and Google Search

Synscribe helps B2B companies with SEO & GEO using programmatic SEO approach. Book a call to find out how we help you win.