Build Phases

Phase 8 — SEO & Metadata

Dynamic per-page metadata and auto-generated sitemaps.

Yoast SEO Integration

Yoast exposes its SEO data via yoast_head_json in the REST API response. Extend your WPPost type to include it:

// Extended type for posts with Yoast data
export interface WPPostWithYoast extends WPPost {
  yoast_head_json: {
    title: string;
    description: string;
    og_image?: Array<{ url: string; width: number; height: number }>;
    canonical: string;
  };
}

// In your [slug]/page.tsx generateMetadata:
export async function generateMetadata({ params }: { params: { slug: string } }) {
  const post = await getPostBySlug(params.slug) as WPPostWithYoast;
  if (!post) return {};
  const y = post.yoast_head_json;
  return {
    title: y?.title ?? post.title.rendered,
    description: y?.description ?? "",
    openGraph: {
      images: y?.og_image?.map(img => ({
        url: img.url,
        width: img.width,
        height: img.height
      })),
    },
    alternates: { canonical: y?.canonical },
  };
}

Auto-generated Sitemap

// src/app/sitemap.ts
import { getAllPosts, getAllProjects } from "@/lib/wordpress";
import { MetadataRoute } from "next";

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const posts    = await getAllPosts(100);
  const projects = await getAllProjects();
  const base     = "https://yourdomain.com";

  return [
    { url: base,               changeFrequency: "daily",   priority: 1 },
    { url: `${base}/blog`,     changeFrequency: "daily",   priority: 0.9 },
    { url: `${base}/projects`, changeFrequency: "weekly",  priority: 0.9 },
    ...posts.map(p => ({
      url: `${base}/blog/${p.slug}`,
      lastModified: new Date(p.modified),
      changeFrequency: "weekly" as const,
      priority: 0.8,
    })),
    ...projects.map(p => ({
      url: `${base}/projects/${p.slug}`,
      lastModified: new Date(p.modified),
      changeFrequency: "monthly" as const,
      priority: 0.7,
    })),
  ];
}

Robots.txt

// src/app/robots.ts
import { MetadataRoute } from "next";

export default function robots(): MetadataRoute.Robots {
  return {
    rules: { userAgent: "*", allow: "/" },
    sitemap: "https://yourdomain.com/sitemap.xml",
  };
}

SEO Checklist

ItemHow
Page titlegenerateMetadata per route
Meta descriptionYoast yoast_head_json.description
Open Graph imageYoast og_image or featured image URL
Canonical URLYoast canonical field
Sitemapsrc/app/sitemap.ts — auto-submits to Google
Robots.txtsrc/app/robots.ts
Structured dataAdd JSON-LD in page components for articles
TIP

After deploying, submit your sitemap to Google Search Console at yourdomain.com/sitemap.xml. This speeds up indexing significantly.

PreviousPhase 7 — DomainsNextPhase 9 — Deployment