All posts

The Next.js App Router SEO Checklist I Actually Use

Metadata, sitemaps, JSON-LD, and the SSR decisions that determine whether Google can actually read your Next.js site — the checklist behind this portfolio.

This portfolio's blog listing page, server-rendered with Next.js

This portfolio is a Next.js 15 App Router site, and every page — including this blog — renders fully on the server so search engines see complete HTML on the first response, no client-side hydration required to reveal content. Here's the checklist I run through for every new page, distilled from actually shipping this site.

1. Server-render the content, isolate the interactivity

The single biggest SEO decision in an App Router app is which components get 'use client'. A page marked client-side loses SSR for everything below it — Google's crawler still executes JavaScript, but you're trading a guaranteed fast, complete HTML response for a dependency on crawl budget and render timing.

The pattern that works: keep the page itself a Server Component, and isolate interactivity (a filter, an accordion, an animated bar) into its own small client component that receives data as props. The text content stays server-rendered; only the interaction ships JavaScript.

2. Per-page metadata, not just root defaults

Every route should export its own metadata (or generateMetadata for dynamic routes) — title, description, canonical URL, and Open Graph data specific to that page:

export const metadata = {
  title: "Post Title",
  description: "A specific, unique description — not a copy of the homepage's.",
  alternates: { canonical: "https://example.com/blog/post-slug" },
  openGraph: { type: "article", title: "...", description: "..." },
};

A canonical URL on every page matters more than it seems — without one, a site risks Google indexing both /blog/post and /blog/post/ (or an old query-string variant) as separate, competing pages.

3. JSON-LD structured data, matched to the content type

Structured data doesn't change what users see, but it changes what Google can understand: a Person schema on the homepage, a BlogPosting schema on each article (headline, datePublished, author, wordCount), and a BreadcrumbList on nested routes. Rich results (author bylines, publish dates in search snippets) come from this, not from visual styling.

4. A real sitemap, generated from real content

A hand-maintained sitemap.xml goes stale the moment you add a page and forget to update it — which, for a growing blog, is basically guaranteed. Next.js's app/sitemap.js convention generates the sitemap from the same data the pages themselves render from, so a new blog post is in the sitemap the moment its markdown file exists, with no separate step to remember.

5. Semantic HTML underneath the design system

None of the above matters if the markup itself is a wall of divs. One h1 per page, article for post content, real nav/header/footer landmarks, and alt text on every image that carries meaning — these cost nothing visually (a well-designed system, like this site's, maps every semantic element to a styled component anyway) but they're what lets a crawler — and a screen reader — understand the page's structure instead of just its pixels.

The result, applied consistently across this site's pages including this one: content indexed the moment it's crawled, no client-side rendering gap, and search snippets that actually reflect what the page is about.

Found this useful? Share it.
Mukarram Javid
Written by

Mukarram Javid

Full-stack .NET & Node.js developer, ASP.NET Core, React, Next.js, and PuppeteerSharp web crawling.