Adios
BlogNext.js SEO

Next.js SEO

How to Configure the Next.js Metadata API: Titles, Canonicals, and Open Graph

Use the Next.js 16 Metadata API for title templates, canonical URLs, dynamic metadata, Open Graph cards, and data-backed App Router pages.

Adios teamUpdated July 17, 20269 min read

Metadata becomes reliable when it comes from the same route data and URL rules as the visible page. Duplicating that logic in a second client-side system creates drift.

Use metadata as route data

A page title, description, canonical URL, and social image describe a particular resource. Generate them beside the route that owns that resource. The root layout should supply stable site defaults; nested layouts can define section conventions; pages should own entity-specific values.

This hierarchy reduces repetition and makes the final title predictable. It also prevents a client component from becoming responsible for document metadata after the first response. The metadata exports work in Server Components, which is the correct boundary for private data access and canonical URL decisions.

Create strong root defaults

Set metadataBase to the production origin, then use relative paths for canonical URLs and images. Define a title template once so child pages supply only the meaningful part. Defaults should be accurate for routes that do not override them, not filler copied to every page.

Keep the site name stable across titles, social cards, and structured data. If staging and production need different public origins, resolve the allowed origin from deployment configuration and fail clearly when it is missing rather than quietly emitting localhost URLs.

export const metadata = {
  metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL),
  title: {
    default: "Northstar",
    template: "%s | Northstar",
  },
  openGraph: {
    siteName: "Northstar",
    type: "website",
  },
};

Generate metadata for dynamic routes

generateMetadata receives asynchronous route parameters in current App Router projects. Fetch the entity, return an explicit not-found title when it does not exist, and build the canonical from its durable slug. Do not use a display name directly in a URL unless the same slugging rules created the route.

The page and metadata often need the same record. Wrap the data function with React cache to deduplicate it during one render pass, or rely on a suitable persistent cache when the content lifetime calls for one. The goal is one source of truth, not two independent API calls that can disagree.

import { cache } from "react";

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

export async function generateMetadata({ params }) {
  const { slug } = await params;
  const post = await getPost(slug);
  if (!post) return { title: "Article not found" };

  return {
    title: post.title,
    description: post.excerpt,
    alternates: { canonical: "/blog/" + post.slug },
  };
}

Write canonical rules before code

Decide whether the site uses www, how trailing slashes behave, which locale owns a page, and whether filter parameters create distinct resources. Then make redirects, internal links, sitemap URLs, and metadata agree. A canonical assembled ad hoc in every page will eventually diverge.

For content whose slug changes, redirect the old URL to the new canonical and preserve a durable identifier in the database. For deleted content, decide whether a related replacement exists; otherwise return a real 404 or 410 rather than a soft-404 page with a successful status.

  • One preferred origin and protocol.
  • One trailing-slash policy.
  • Explicit locale alternates where translated pages truly exist.
  • A redirect plan for changed slugs and retired content.

Build social cards that match the page

Open Graph and Twitter fields should use the page title, concise description, and an image that remains legible when cropped. A generic site image is an acceptable fallback, but articles and products benefit from entity-specific cards. Supply dimensions and meaningful alt text.

Static images can use the opengraph-image file convention. Dynamic routes can generate an image with ImageResponse or reference an image stored with the entity. Treat image generation as production code: validate long titles, missing images, font loading, and the runtime cost.

return {
  openGraph: {
    type: "article",
    title: post.title,
    description: post.excerpt,
    images: [{
      url: post.ogImage,
      width: 1200,
      height: 630,
      alt: post.title,
    }],
  },
};

Handle robots and missing entities on the server

A draft, private record, or internal search page should not become indexable because the browser later hides it. Return the correct metadata and status from the server. If an entity is not public, do not leak its title or description in metadata before authorization is checked.

Robots metadata is not access control. Private routes still require authentication and authorization. For public preview links, use unguessable identifiers, noindex metadata, and clear lifecycle rules, then ensure previews are absent from the sitemap and internal navigation.

Test the rendered document

Inspect the browser document head, but also fetch the raw response and view page source. Confirm that the final title, description, canonical, robots directive, Open Graph values, and JSON-LD are correct for a direct request. Test a known entity, a missing entity, a draft, and a changed slug.

Automate the stable parts. A route smoke test can assert the status, canonical URL, title fragment, and absence of preview origins. Keep content-quality review human; a test can prove a description exists but cannot prove it is useful.

Verify metadata before domain promotion

Deploy the candidate to an Adios-generated HTTPS route and inspect its head output using the production build. Keep the public site origin in reviewable configuration and sensitive provider values behind secret references. Build and runtime logs remain attached to the candidate if metadata generation fails.

Once the release responds successfully and its metadata is correct, connect or retain the canonical custom domain. Managed TLS and release promotion keep the public hostname attached to the version that passed the checks instead of making DNS changes part of every content release.

env:
  NEXT_PUBLIC_SITE_URL: https://www.example.com

runtime:
  name: node@24
  port: 3000
  health_path: /api/health
  All articles