DEV3 Technologies home

DEV3 Developer Forum

How do I set dynamic title, description and Open Graph tags in the Next.js App Router?

Next.js helpSolved0 views
dev3 ·

I need per-page metadata (title, description, canonical, og:image) for blog posts loaded from a database. What is the right API in the App Router and how do I avoid fetching twice?

#nextjs#seo#metadata

0

1 reply

dev3 · Accepted answer

Export generateMetadata from the page. Next merges it with parent layouts.

// app/blog/[slug]/page.tsx
import type { Metadata } from "next";
import { cache } from "react";
import { notFound } from "next/navigation";

const getPost = cache(async (slug: string) => db.post.findUnique({ where: { slug } }));

export async function generateMetadata(
  { params }: { params: Promise<{ slug: string }> }
): Promise<Metadata> {
  const { slug } = await params;
  const post = await getPost(slug);
  if (!post) return {};
  return {
    title: post.title,
    description: post.excerpt,
    alternates: { canonical: `/blog/${slug}` },
    openGraph: {
      title: post.title,
      description: post.excerpt,
      type: "article",
      images: [{ url: post.cover, width: 1200, height: 630 }],
    },
    twitter: { card: "summary_large_image" },
  };
}

export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
  const post = await getPost((await params).slug);
  if (!post) notFound();
  return <article>{post.title}</article>;
}

Avoid double fetching

Wrap the loader in React cache() (as above) so generateMetadata and the page share one query per request. Plain fetch calls are memoised automatically.

Base URL

Set metadataBase once in the root layout so relative URLs resolve:

export const metadata: Metadata = {
  metadataBase: new URL("https://example.com"),
  title: { default: "Example", template: "%s | Example" },
};

Other files

  • app/sitemap.ts and app/robots.ts generate those files.
  • app/opengraph-image.tsx renders OG images with ImageResponse.
  • JSON-LD: embed the structured data as a JSON-LD script tag using JSON.stringify(data) in the page.

Pitfall: notFound() inside generateMetadata is fine, but returning metadata for a missing record produces soft-404 pages.

Preview the result with the meta tag preview builder.

0

Your reply

Every post is reviewed by DEV3 before it appears. Usually reviewed within 24 hours.

Markdown supported. Use ``` for code blocks. Images are not allowed.