Mark Lowel Montealto — Full Stack Developer & DevOps Engineer

Mark Lowel
Montealto

Full Stack Developer & DevOps Engineer

Blog

July 2024

·

3 min read

Next.js App Router SEO: Server-Side Rendering Done Right

Use the Next.js App Router metadata API, JSON-LD structured data, and SSR to ship crawlable pages that rank — from generateMetadata to sitemaps.

Introduction

  • Why SEO needs SSR: crawlers receive fully-rendered HTML; client-rendered SPAs often miss the first-crawl window.
  • App Router defaults to Server Components — every page is SSR out of the box.
  • This guide covers the metadata API, Open Graph, JSON-LD, sitemaps, robots, and performance signals that affect rankings.

How the App Router Renders Pages for Crawlers

  • Every page.tsx in the App Router is a Server Component — HTML is generated at request time (or build time for static).
  • Crawlers like Googlebot receive the complete document; no JavaScript execution required for core content.
  • fetch cache and revalidate control freshness: use revalidate: 3600 for content that changes hourly.

The Metadata API: generateMetadata and Static metadata

  • Export metadata (static) or generateMetadata (dynamic) from any page.tsx or layout.tsx.
  • Required fields for every page: title, description, alternates.canonical.
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next';
 
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {
  const { slug } = await params;
  const post = await getPostBySlug(slug);
 
  return {
    title: post.title,
    description: post.excerpt,
    alternates: { canonical: `/blog/${slug}` },
    openGraph: {
      title: `${post.title} — Mark Lowel Montealto`,
      description: post.excerpt,
      type: 'article',
      images: [{ url: post.coverImage ?? '/og.png', width: 1200, height: 630 }],
    },
    twitter: {
      card: 'summary_large_image',
      title: post.title,
      description: post.excerpt,
    },
  };
}

The Title Template Pattern

  • Root layout sets title: { template: '%s | Mark Lowel Montealto', default: 'Mark Lowel Montealto' }.
  • Child pages provide a short title — the template appends the brand.
  • Result: "Next.js App Router SEO | Mark Lowel Montealto" — good for both SERP snippets and brand recognition.

Open Graph & Twitter Card

  • OG images must be 1200×630 px; use Next.js ImageResponse (app/opengraph-image.tsx) to generate them dynamically.
  • Always re-declare images in child pages — Next.js replaces (does not deep-merge) parent openGraph.
  • Twitter card: summary_large_image for blog posts; summary for smaller pages.

JSON-LD Structured Data

  • Inject via a <JsonLd> component that renders a <script type="application/ld+json"> in the <head>.
  • Root layout: WebSite + Person schemas for brand entity.
  • Blog posts: BlogPosting schema with headline, author, datePublished, image.
// components/JsonLd.tsx
export function JsonLd({ data }: { data: object }) {
  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }}
    />
  );
}

sitemap.ts and robots.ts

  • Next.js App Router supports app/sitemap.ts (returns MetadataRoute.Sitemap).
  • Dynamically fetch all post slugs and generate URL entries with lastModified and changeFrequency.
  • app/robots.ts returns { rules: { userAgent: '*', allow: '/' }, sitemap: '...' }.
// app/sitemap.ts
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const posts = await getPosts();
  return posts.map(post => ({
    url: `https://marklowelmontealto.com/blog/${post.slug}`,
    lastModified: new Date(post.date),
    changeFrequency: 'monthly',
    priority: 0.8,
  }));
}

Core Web Vitals and SEO

  • Google's Page Experience ranking signals: LCP, INP, CLS.
  • Next.js <Image> auto-optimises format, size, and lazy loading — use it for every image.
  • next/font eliminates FOUT (Flash of Unstyled Text) — a common CLS source.
  • Route prefetching via <Link> ensures fast navigations (INP).

Robots Meta and noindex Pages

  • Admin, draft, and tag pagination pages should have robots: { index: false }.
  • Apply via metadata export or programmatically in generateMetadata.
  • Never noindex canonical content pages.

Checklist: Before Every Deploy

  • Every page has title, description, alternates.canonical.
  • OG image defined (1200×630, alt text).
  • JSON-LD present on homepage and all blog posts.
  • sitemap.ts includes all public routes.
  • robots.ts allows all crawlers, lists sitemap URL.
  • <Image> used for every img — no bare <img> tags.
  • Run Lighthouse SEO audit — target 100.

Conclusion

  • The App Router's metadata API covers 90% of SEO needs with minimal boilerplate.
  • JSON-LD + canonical URLs + Core Web Vitals are the remaining 10% that move the needle for competitive terms.
  • Treat SEO as a first-class concern from day one — retrofitting metadata onto 50 pages is painful.