Tutorial

Tutorial: SEO metadata in Next.js App Router

July 12, 2026 · Kabim Tech SEO · 2 min read

← Back to blog
SEO metadata and Open Graph preview layout
SEO metadata and Open Graph preview layout
Next.js Metadata API overview
  • Seo
  • Nextjs
  • Metadata

Set title, description, Open Graph, Twitter cards, and canonical URLs with the Next.js Metadata API so every page is search and share ready.

Search engines and social platforms read your metadata before they read your design. This tutorial wires production-ready SEO metadata in the Next.js App Router using the Metadata API.

What you will build

  • Root `metadata` and `generateMetadata` for dynamic pages
  • Open Graph and Twitter card tags
  • Absolute canonical URLs from a shared site config
  • A reusable helper so blog and marketing pages stay consistent

Prerequisites

  • Next.js 14+ App Router project
  • A public site URL (e.g. `https://kabimtech.com`)
  • Basic TypeScript

Step-by-step

  1. Add `lib/seo/site-url.ts` that returns your absolute origin from `NEXT_PUBLIC_SITE_URL`.
  2. In `app/layout.tsx`, export a base `metadata` object with `metadataBase`, default `title.template`, and site-wide `description`.
  3. For listing pages (`/blog`, `/services`), export static `metadata` with a unique title and description.
  4. For dynamic routes (`/blog/[slug]`), export `generateMetadata` that loads the post and returns title, description, `openGraph.images`, and `alternates.canonical`.
  5. Always pass absolute image URLs (or rely on `metadataBase` so relative paths resolve).
  6. Verify with View Source and the Facebook Sharing Debugger / X Card Validator.

Root metadata

import type { Metadata } from "next";

export const metadata: Metadata = {
  metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL!),
  title: {
    default: "Kabim Technologies",
    template: "%s | Kabim Tech",
  },
  description:
    "Product engineering, developer rent, and dedicated teams from Kathmandu, Nepal.",
  openGraph: {
    type: "website",
    locale: "en_US",
    siteName: "Kabim Technologies",
  },
  twitter: {
    card: "summary_large_image",
  },
};

Dynamic blog metadata

export async function generateMetadata({
  params,
}: {
  params: Promise<{ slug: string }>;
}): Promise<Metadata> {
  const { slug } = await params;
  const post = await getPostBySlug(slug);
  if (!post) return { title: "Post not found" };

  return {
    title: post.title,
    description: post.excerpt,
    alternates: { canonical: `/blog/${post.slug}` },
    openGraph: {
      title: post.title,
      description: post.excerpt,
      type: "article",
      publishedTime: post.publishedAt,
      images: [{ url: post.coverImage, alt: post.coverImageAlt }],
    },
  };
}

Checklist

  • Unique title and description per indexable URL
  • Canonical points to the preferred URL (no query-string duplicates)
  • OG image at least 1200×630
  • No `noindex` on pages you want ranked

Next steps

Add JSON-LD (`Organization`, `BlogPosting`) in a server component so rich results can pick up structured data alongside your meta tags.

Want to talk about your product?

Need a dedicated squad or a fixed-scope MVP? We help teams ship on time.