🔍 Advanced SEO in Action: Dynamic Metadata and Open Graph

If you've built an awesome blog or e-commerce site but only see an ugly URL with no attractive thumbnail or title when shared on Facebook or LINE groups, your click-through rate will undoubtedly suffer.

In the traditional React (Client-side Rendering) era, solving this problem was painful, requiring additional servers or packages for prerendering.
But congratulations! In the Next.js App Router world, SEO optimization is not only built-in but ridiculously simple!

In this chapter, we’ll take you deep into the Metadata API, teaching you how to dynamically generate titles, descriptions, and automatically create social sharing preview images (Open Graph Image).


1. Static Metadata Configuration

If you only need to set titles for the homepage or specific fixed pages, simply export a metadata constant in the corresponding page.tsx or layout.tsx.

// src/app/layout.tsx or src/app/page.tsx
import { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'Vibe Tutor Knowledge Platform',
  description: 'The most powerful SaaS development course in Taiwan, teaching you to build a money-making machine with Next.js!',
  keywords: ['Next.js', 'React', 'SaaS', 'Tutorial', 'Vibe Tutor'],
  authors: [{ name: 'Vibe Coder' }],
  // Open Graph settings for Facebook, LINE, etc.
  openGraph: {
    title: 'Vibe Tutor Knowledge Platform',
    description: 'The most powerful SaaS development course in Taiwan',
    url: 'https://vibe-tutor.com',
    siteName: 'Vibe Tutor',
    images: [
      {
        url: 'https://vibe-tutor.com/og-image.jpg',
        width: 1200,
        height: 630,
        alt: 'Vibe Tutor Course Preview',
      },
    ],
    locale: 'zh_TW',
    type: 'website',
  },
  // Twitter settings
  twitter: {
    card: 'summary_large_image',
    title: 'Vibe Tutor Knowledge Platform',
    description: 'The most powerful SaaS development course in Taiwan',
    creator: '@vibecoder',
    images: ['https://vibe-tutor.com/twitter-image.jpg'],
  },
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  // ...
}

💡 Pro Tip: Title Templates

You can set a title template in layout.tsx so child pages don’t need to repeat the brand name:

// src/app/layout.tsx
export const metadata: Metadata = {
  title: {
    template: '%s | Vibe Tutor', // %s will be replaced by child pages
    default: 'Vibe Tutor Knowledge Platform', // Fallback if child pages don’t set a title
  },
};

// src/app/pricing/page.tsx
export const metadata: Metadata = {
  title: 'Pricing Plans', // Final title becomes "Pricing Plans | Vibe Tutor"
};

2. Dynamic Metadata (generateMetadata)

If you have a blog post page /blog/[slug]/page.tsx where each post has a different title, what should you do?
In this case, you can’t use a constant metadata. Instead, you must export an asynchronous function generateMetadata.

Next.js will execute this function before rendering the page, fetch data from the database, and generate the corresponding <title> and <meta> tags in the HTML <head>!

// src/app/blog/[slug]/page.tsx
import { Metadata, ResolvingMetadata } from 'next';
import { db } from '@/lib/db'; // Your database

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

// Dynamically generate Metadata
export async function generateMetadata(
  { params }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  // 1. Fetch the post from the database
  const post = await db.post.findUnique({ where: { slug: params.slug } });

  // If the post doesn’t exist, return a 404 title
  if (!post) {
    return {
      title: 'Post Not Found',
    };
  }

  // Optionally access and extend (rather than replace) parent metadata
  const previousImages = (await parent).openGraph?.images || [];

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      // Use the post's cover image if available, otherwise fall back to parent defaults
      images: post.coverImage ? [post.coverImage] : previousImages, 
    },
  };
}

export default async function BlogPostPage({ params }: Props) {
  // Actual page rendering logic
  const post = await db.post.findUnique({ where: { slug: params.slug } });
  // ...
}

💡 Performance Concerns? Next.js Has You Covered!

You might ask: "The database is queried once in generateMetadata and again in BlogPostPage. Isn’t that wasteful?"
Don’t worry! Next.js uses fetch caching and React’s cache mechanism under the hood. For the same Request/URL/query, it only executes once, and subsequent calls return cached results—zero performance impact!


3. The Ultimate Weapon: Automatic OG Image Generation

Have you noticed how Vercel or GitHub article links automatically display the post title on their shared images?
This is called Dynamic Open Graph Image.

In the past, implementing this required third-party APIs or Headless Chrome screenshots—so complex it made you want to give up.
Now, Next.js natively supports @vercel/og. Just write JSX, and it automatically renders it into an image!

Step 1: Create an opengraph-image.tsx File

In your route folder (e.g., src/app/blog/[slug]/), create a special file named opengraph-image.tsx (or .alt.txt / .tsx with ImageResponse).

// src/app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og';
import { db } from '@/lib/db';

// Configure image size
export const runtime = 'edge';
export const alt = 'Blog Post Cover Image';
export const size = {
  width: 1200,
  height: 630,
};
export const contentType = 'image/png';

export default async function Image({ params }: { params: { slug: string } }) {
  // Fetch the post title
  const post = await db.post.findUnique({ where: { slug: params.slug } });
  const title = post?.title || 'Amazing Article';

  return new ImageResponse(
    (
      // Write JSX here! It’ll be converted into an image!
      <div
        style={{
          fontSize: 80,
          background: 'linear-gradient(to bottom right, #4f46e5, #ec4899)', // Cool gradient
          color: 'white',
          width: '100%',
          height: '100%',
          display: 'flex',
          flexDirection: 'column',
          alignItems: 'center',
          justifyContent: 'center',
          padding: '80px',
          textAlign: 'center',
          fontWeight: 800,
        }}
      >
        {/* Display the dynamic title from the database! */}
        <div style={{ marginBottom: 40 }}>{title}</div>
        
        {/* Add your brand logo or text */}
        <div style={{ fontSize: 40, color: 'rgba(255,255,255,0.8)' }}>
          🚀 Vibe Tutor Hands-on Tutorial
        </div>
      </div>
    ),
    {
      ...size,
    }
  );
}

That’s it! No additional setup required. When someone shares your article link on Facebook:
https://your-domain.com/blog/my-awesome-post
Facebook’s crawler will automatically fetch this stunning gradient-background + large-title image, dynamically generated from JSX!

This is why Next.js is hailed as the ultimate choice for SEO and commercial websites. Master Metadata and OG Image, and your site’s traffic will skyrocket! 📈

Unlock Full Tutorial

This chapter is paid content. Join the project to unlock over 5000 words of deep analysis, including 10+ god-tier Prompts and real Source Code examples!