| name | examples |
| description | Complete SEO recipes and implementation examples for Next.js App Router. This skill should be used when the user asks for "SEO example", "blog SEO setup", "e-commerce SEO", "landing page SEO", "SaaS SEO", "full SEO implementation", "SEO recipe", "SEO template", or needs a complete, copy-paste ready SEO implementation for a specific page type. |
SEO Examples & Recipes
Complete, production-ready SEO implementations for common page types. Each recipe includes metadata, structured data, and component patterns.
Recipe 1: Blog with Article Schema
Root Layout (Sitewide SEO)
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import { JsonLd } from '@/components/json-ld'
import { createOrganization, createWebSite } from '@/lib/schema'
const inter = Inter({ subsets: ['latin'], display: 'swap', variable: '--font-sans' })
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://yourblog.com'
export const metadata: Metadata = {
metadataBase: new URL(SITE_URL),
title: { template: '%s | Your Blog', default: 'Your Blog' },
description: 'Insights on web development, SEO, and modern tooling.',
openGraph: {
type: 'website',
siteName: 'Your Blog',
locale: 'en_US',
},
twitter: { card: 'summary_large_image' },
alternates: { types: { 'application/rss+xml': '/feed.xml' } },
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={inter.variable}>
<body>
<JsonLd data={createOrganization({
name: 'Your Blog', logo: `${SITE_URL}/logo.png`, url: SITE_URL,
sameAs: ['https://twitter.com/yourblog', 'https://github.com/yourblog'],
})} />
<JsonLd data={createWebSite(SITE_URL, 'Your Blog')} />
{children}
</body>
</html>
)
}
Blog Post Page
import type { Metadata } from 'next'
import Image from 'next/image'
import { JsonLd } from '@/components/json-ld'
import { createArticle, createBreadcrumbList } from '@/lib/schema'
import { Breadcrumbs } from '@/components/breadcrumbs'
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL!
interface Props { params: Promise<{ slug: string }> }
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
const post = await getPost(slug)
if (!post) return {}
return {
title: post.title,
description: post.excerpt,
openGraph: {
: post.,
: post.,
: ,
: ,
: post.,
: post.,
: [post..],
: [{ : post., : , : , : post. }],
},
: { : },
}
}
() {
posts = ()
posts.( ({ slug }))
}
() {
{ slug } = params
post = (slug)
(
)
}
Blog Sitemap
import type { MetadataRoute } from 'next'
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL!
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await getAllPosts()
return posts.map((post) => ({
url: `${SITE_URL}/blog/${post.slug}`,
lastModified: new Date(post.updatedAt),
changeFrequency: 'weekly',
priority: 0.7,
}))
}
Recipe 2: E-commerce Product Page
import type { Metadata } from 'next'
import Image from 'next/image'
import { JsonLd } from '@/components/json-ld'
import { createProduct, createBreadcrumbList } from '@/lib/schema'
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL!
interface Props { params: Promise<{ slug: string }> }
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
const product = await getProduct(slug)
if (!product) return {}
return {
title: `${product.name} — ${product.brand}`,
description: `${product.name} by ${product.brand}. ${product.shortDescription}. $.`,
: {
: product.,
: product.,
: ,
: ,
: product..( ({
: img, : , : , : product.,
})),
},
: { : },
}
}
() {
{ slug } = params
product = (slug)
(
)
}
Recipe 3: Landing Page
import type { Metadata } from 'next'
import Image from 'next/image'
export const metadata: Metadata = {
title: 'Your Product — Tagline in 60 chars',
description: 'Clear value proposition in 150-160 characters. Explain what the product does and who it is for.',
openGraph: {
title: 'Your Product — Tagline',
description: 'Clear value proposition for social sharing.',
url: '/',
type: 'website',
images: [{ url: '/og/home.png', width: 1200, height: 630, alt: 'Your Product' }],
},
alternates: { canonical: '/' },
}
export default function Home() {
return (
<>
<section>
<h1>Your Product Headline</h1>
<>Value proposition paragraph explaining the core benefit.
Features
{/* Feature blocks with H3 subheadings */}
How It Works
{/* Step-by-step with numbered items */}
)
}
Recipe 4: SaaS Pricing Page
import type { Metadata } from 'next'
import type { SoftwareApplication, WithContext } from 'schema-dts'
import { JsonLd } from '@/components/json-ld'
export const metadata: Metadata = {
title: 'Pricing',
description: 'Simple, transparent pricing. Free tier available. Pro from $29/mo. Enterprise custom.',
openGraph: {
title: 'Pricing — Your Product',
description: 'Simple, transparent pricing for teams of all sizes.',
url: '/pricing',
type: 'website',
},
alternates: { canonical: '/pricing' },
}
export default function PricingPage() {
const softwareSchema: WithContext<SoftwareApplication> = {
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
name: 'Your Product',
: ,
: ,
: [
{
: ,
: ,
: ,
: ,
},
{
: ,
: ,
: ,
: ,
: ,
},
],
}
(
)
}
Recipe 5: Dynamic OG Image for Any Page
import { ImageResponse } from 'next/og'
import { readFile } from 'node:fs/promises'
import { join } from 'node:path'
export const alt = 'Blog post preview'
export const size = { width: 1200, height: 630 }
export const contentType = 'image/png'
export default async function Image({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const post = await getPost(slug)
const font = await readFile(join(process.cwd(), 'public/fonts/Inter-Bold.ttf'))
return new ImageResponse(
(
<div style={{
display: 'flex', flexDirection: 'column', '',
'%', '%', '#', '#', ,
}}>
{post.title}
{post.excerpt}
{post.author.name}
{new Date(post.publishedAt).toLocaleDateString()}
),
{ ...size, : [{ : , : font, : , : }] }
)
}
Common Utility: schema.ts
See the structured-data skill for the complete lib/schema.ts factory functions. All recipes above import from this shared file.
Common Utility: json-ld.tsx
import type { Thing, WithContext } from 'schema-dts'
export function JsonLd<T extends Thing>({ data }: { data: WithContext<T> }) {
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(data).replace(/</g, '\\u003c'),
}}
/>
)
}