afaqrashid.com  ·  portfolio · 2026 ed.

Next.js metadata generator

Fill in your site’s details and copy the code into your root layout. It runs in your browser and updates as you type.

Your production domain
%s is replaced by each page's title
A path in /public, 1200 × 630
Optional
Optional

Ready to paste into app/layout.tsx.

app/layout.tsx
import type { Metadata, Viewport } from "next";

export const metadata: Metadata = {
  metadataBase: new URL("https://example.com"),
  title: {
    default: "Example: Project Tracking for Small Teams",
    template: "%s | Example",
  },
  description: "Track projects, deadlines and files in one place. Built for teams of 2 to 20, with a free plan.",
  applicationName: "Example",
  // "./" gives every page its own canonical URL, without the query string.
  alternates: { canonical: "./" },
  // No title or description here, so each page's own are used.
  openGraph: {
    type: "website",
    siteName: "Example",
    locale: "en_US",
    url: "./",
    images: [{ url: "/og-image.png", width: 1200, height: 630, alt: "Example project board" }],
  },
  twitter: { card: "summary_large_image" },
  robots: { index: true, follow: true },
};

// viewport and themeColor go in their own export, not in metadata.
export const viewport: Viewport = {
  width: "device-width",
  initialScale: 1,
  themeColor: "#ffffff",
};
Each page
// app/about/page.tsx
export const metadata: Metadata = {
  title: "About",
  description: "What this page is about, in one sentence.",
};

// app/pricing/page.tsx
// A page that sets openGraph replaces the layout's whole object,
// so repeat the shared values with it.
export const metadata: Metadata = {
  title: "Pricing",
  openGraph: {
    type: "website",
    siteName: "Example",
    locale: "en_US",
    url: "./",
    images: [{ url: "/og-image.png", width: 1200, height: 630 }],
    title: "Pricing: free for small teams",
  },
};

Why the code looks like this

One canonical line covers every page. canonical: "./" in the root layout gave each page in my Next.js 16 tests its own canonical URL, dynamic routes included, and dropped the query string. Write it with the slash: a plain "." points every page at your home page. The tests are in the post on getting the current URL in generateMetadata.

The layout’s openGraph has no title or description. When it leaves them out, Next.js fills og:title, og:description and the X tags from each page’s own title and description. Set a title there and every page that doesn’t override it shares your home page title.

A page that sets openGraph loses the layout’s. Next.js merges metadata one top-level key at a time, so that page drops the share image, URL and site name unless it repeats them. The second code block shows how. More in why Next.js share images go missing.

viewport is its own export. Put viewport or themeColor inside metadata and the build only warns, while the page keeps the default viewport tag. See the unsupported metadata viewport fix.

After you paste it

Deploy, open any page, choose View Page Source and paste the HTML into the Next.js metadata checker. Then check your sitemap and robots.txt.