Next.js App Router SEO

The Next.js App Router makes server rendering the default and replaces the old next/head approach with a declarative Metadata API. For SEO this is a strong foundation: routes ship populated HTML and metadata in the response, and you express per-route titles, descriptions, and structured data through typed exports rather than imperative head manipulation. This guide covers the App Router’s SEO surface within framework-specific SEO implementations, applying the rendering-strategy decisions in Next’s model.

Prerequisites

  • Next.js 13+ using the app/ directory (App Router).
  • Route segments organized so metadata can be colocated with each page.
  • An understanding of the fetch cache and revalidate for per-route rendering mode.
  • Search Console access to verify rendered metadata.

How it breaks

The App Router’s most common SEO regression is marking a layout or page 'use client' at too high a level, which opts whole subtrees out of server metadata and pushes rendering toward the client. Another is putting generateMetadata work behind an uncached, slow fetch so the route’s TTFB balloons and rendering stalls.

Next.js App Router metadata flow A request triggers generateMetadata and the server component render; both resolve on the server and emit the head and HTML in the response. request generateMetadata() async, per route server component renders content response head + HTML bot
generateMetadata and the server component both resolve server-side and emit the head and HTML together in the response.

Step-by-step fix

  1. Export static metadata for fixed routes.

    // app/about/page.jsx
    export const metadata = {
      title: 'About — Example',
      description: 'Who we are and what we build.',
      alternates: { canonical: 'https://example.com/about' },
    };
  2. Use generateMetadata for data-driven routes. Detailed in dynamic metadata with the Next.js Metadata API.

    // app/products/[id]/page.jsx
    export async function generateMetadata({ params }) {
      const product = await getProduct(params.id);
      return { title: product.name, description: product.summary };
    }
  3. Set the rendering mode per route with the fetch cache.

    export const revalidate = 3600; // ISR; 0 = SSR; force-static = SSG
  4. Keep 'use client' low in the tree so metadata and content stay server-rendered.

Gotchas & edge cases

  • 'use client' on a layout. Opts whole subtrees out of server metadata; push the directive down to leaf interactive components.
  • Slow uncached fetch in generateMetadata. Blocks the response; cache or deduplicate the fetch (Next dedupes identical fetch calls within a request).
  • Missing canonical. Set alternates.canonical per route to avoid duplicates — see canonical URL management.
  • JSON-LD placement. Render a <script type="application/ld+json"> in the server component body; it ships in the response.

Validation checklist

Performance & crawl-budget notes

App Router routes render metadata and content on the server, so they index in the first wave and skip the render queue, protecting crawl budget. The fetch cache lets you serve most routes as static or ISR and reserve full SSR for genuinely dynamic ones, keeping server cost proportional to how dynamic each route really is.

Go deeper

Frequently Asked Questions

How does SEO work in the Next.js App Router? The App Router renders on the server by default and exposes a Metadata API: you export a static metadata object or an async generateMetadata function per route segment, and Next renders the resulting tags into the document head in the response. Rendering mode is controlled per route via the fetch cache and route segment config.

Do I need next/head in the App Router? No. next/head is a Pages Router API. In the App Router you use the Metadata API — the metadata export or generateMetadata — instead, which is server-rendered and typed.

← Back to Framework-Specific SEO Implementations