Agent skill
SEO Optimization
Implement Next.js SEO with metadata, structured data, sitemaps, Open Graph tags, and technical SEO. Apply when optimizing pages for search engines, adding social sharing, or improving discoverability.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/development/seo-optimization
SKILL.md
SEO Optimization
Systematic SEO implementation for Next.js applications ensuring maximum search visibility and social sharing.
Overview
This Skill enforces:
- Optimized metadata (titles, descriptions)
- Open Graph and Twitter Card tags
- JSON-LD structured data
- Sitemaps and robots.txt
- Canonical tags for duplicate prevention
- Image optimization
- Core Web Vitals optimization
- URL structure best practices
Apply when optimizing pages for search engines, adding social sharing, or improving discoverability.
Metadata Optimization
Static Metadata
// app/page.tsx
export const metadata: Metadata = {
title: 'Home | My App',
description: 'Build amazing apps with modern technology',
keywords: ['Next.js', 'React', 'TypeScript'],
authors: [{ name: 'Your Name' }],
viewport: 'width=device-width, initial-scale=1',
robots: 'index, follow'
};
export default function HomePage() {
return <div>Home</div>;
}
Dynamic Metadata (generateMetadata)
// app/blog/[slug]/page.tsx
import { Metadata } from 'next';
export async function generateMetadata(
{ params }: { params: { slug: string } }
): Promise<Metadata> {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.excerpt,
keywords: post.tags,
openGraph: {
title: post.title,
description: post.excerpt,
url: `https://example.com/blog/${post.slug}`,
siteName: 'My Blog',
images: [
{
url: post.image,
width: 1200,
height: 630,
alt: post.title
}
],
type: 'article',
publishedTime: post.publishedAt,
authors: [post.author]
}
};
}
export default async function BlogPost(
{ params }: { params: { slug: string } }
) {
const post = await getPost(params.slug);
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
Open Graph & Social Sharing
Complete Open Graph Setup
// app/layout.tsx
export const metadata: Metadata = {
metadataBase: new URL('https://example.com'),
title: 'My App',
description: 'The best app ever',
openGraph: {
type: 'website',
locale: 'en_US',
url: 'https://example.com',
siteName: 'My App',
title: 'My App',
description: 'The best app ever',
images: [
{
url: '/og-image.png',
width: 1200,
height: 630,
alt: 'My App'
}
]
},
twitter: {
card: 'summary_large_image',
title: 'My App',
description: 'The best app ever',
images: ['/og-image.png'],
creator: '@yourhandle'
}
};
Article Metadata (Blog Post)
const metadata: Metadata = {
openGraph: {
type: 'article',
publishedTime: '2025-01-15T10:00:00Z',
modifiedTime: '2025-01-20T15:30:00Z',
authors: ['John Doe'],
tags: ['Next.js', 'SEO', 'Performance']
}
};
JSON-LD Structured Data
Product Schema
// app/products/[id]/page.tsx
export default function ProductPage({ params }) {
const product = getProduct(params.id);
const structuredData = {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
description: product.description,
image: product.image,
brand: {
'@type': 'Brand',
name: 'My Store'
},
offers: {
'@type': 'Offer',
price: product.price,
priceCurrency: 'USD',
availability: 'https://schema.org/InStock'
},
aggregateRating: {
'@type': 'AggregateRating',
ratingValue: product.rating,
reviewCount: product.reviews.length
}
};
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
/>
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>Price: ${product.price}</p>
</div>
</>
);
}
Organization Schema
// app/layout.tsx
export default function RootLayout({ children }) {
const organizationSchema = {
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'My Company',
url: 'https://example.com',
logo: 'https://example.com/logo.png',
description: 'Company description',
sameAs: [
'https://twitter.com/mycompany',
'https://linkedin.com/company/mycompany',
'https://facebook.com/mycompany'
],
contact: {
'@type': 'ContactPoint',
contactType: 'Customer Support',
email: 'support@example.com'
}
};
return (
<html>
<head>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema) }}
/>
</head>
<body>{children}</body>
</html>
);
}
Sitemaps
Static Sitemap
// app/sitemap.ts
import { MetadataRoute } from 'next';
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: 'https://example.com',
lastModified: new Date(),
changeFrequency: 'yearly',
priority: 1
},
{
url: 'https://example.com/about',
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.8
},
{
url: 'https://example.com/contact',
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.8
}
];
}
Dynamic Sitemap (Blog Posts)
// app/sitemap.ts
import { MetadataRoute } from 'next';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await getAllPosts();
const postUrls = posts.map(post => ({
url: `https://example.com/blog/${post.slug}`,
lastModified: post.updatedAt || post.publishedAt,
changeFrequency: 'weekly' as const,
priority: 0.7
}));
const staticRoutes: MetadataRoute.Sitemap = [
{
url: 'https://example.com',
lastModified: new Date(),
changeFrequency: 'yearly' as const,
priority: 1
},
{
url: 'https://example.com/blog',
lastModified: new Date(),
changeFrequency: 'daily' as const,
priority: 0.8
}
];
return [...staticRoutes, ...postUrls];
}
Robots.txt
// app/robots.ts
import { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{
userAgent: '*',
allow: '/',
disallow: ['/admin', '/api', '/private']
},
{
userAgent: 'Googlebot',
allow: '/',
crawlDelay: 0
}
],
sitemap: 'https://example.com/sitemap.xml',
host: 'https://example.com'
};
}
Canonical Tags
export const metadata: Metadata = {
alternates: {
canonical: 'https://example.com/page'
}
};
// ✅ GOOD: Prevent duplicate content
// If /blog/post and /blog?id=1 show same content
// Set canonical on both pointing to primary URL
// ❌ BAD: No canonical tag
// Search engines may index duplicate content
URL Structure
Best Practices
✅ GOOD: Clean, descriptive URLs
/blog/seo-best-practices
/products/laptop-15-inch
/docs/getting-started
❌ BAD: Query parameters for content
/blog?id=123&post=abc
/products?item=laptop
/page.php?section=intro
✅ GOOD: Hierarchical structure
/blog
/blog/web-development
/blog/web-development/seo-tips
❌ BAD: Deep unnecessary nesting
/content/articles/posts/blogs/my-post
/docs/guide/documentation/how-to/step-by-step
Image Optimization for SEO
import Image from 'next/image';
// ✅ GOOD: Optimized image with alt text
<Image
src="/blog-post-image.jpg"
alt="Complete guide to Next.js SEO optimization"
width={1200}
height={630}
priority={false}
quality={80}
format="webp"
/>
// ❌ BAD: Missing alt text
<img src="/blog-post-image.jpg" />
// ❌ BAD: Generic alt text
<Image
src="/image.jpg"
alt="image"
/>
Core Web Vitals Optimization
Lighthouse Score Target
- LCP (Largest Contentful Paint): < 2.5 seconds
- FID (First Input Delay): < 100 milliseconds
- CLS (Cumulative Layout Shift): < 0.1
Optimization Steps
// ✅ GOOD: Preload critical resources
export const metadata = {
metadataBase: new URL('https://example.com'),
preload: [
{
href: '/fonts/inter-var.woff2',
as: 'font',
type: 'font/woff2',
crossOrigin: 'anonymous'
}
]
};
// ✅ GOOD: Lazy load below-fold images
<Image
src="/below-fold-image.jpg"
alt="description"
loading="lazy"
/>
// ✅ GOOD: Dynamic imports for heavy components
const HeavyChart = dynamic(() => import('@/components/Chart'), {
loading: () => <div>Loading...</div>
});
Verification Checklist
Before Deploying
- Title tag: 50-60 characters, keyword-rich
- Meta description: 155-160 characters, compelling
- Headings: Proper hierarchy (h1 > h2 > h3)
- Canonical tags: Prevent duplicate content
- Open Graph tags: Social sharing optimized
- JSON-LD schema: Rich results enabled
- Sitemap: Generated and valid
- Robots.txt: Proper rules configured
- Images: Alt text, optimized, WebP format
- Core Web Vitals: LCP < 2.5s, FID < 100ms, CLS < 0.1
- Mobile-friendly: Responsive design verified
- No broken links: 404 redirects handled
Testing Tools
# Google Lighthouse
# https://pagespeed.web.dev
# Google Search Console
# https://search.google.com/search-console
# Structured Data Testing
# https://schema.org/validator
# Open Graph Preview
# https://www.opengraphcheck.com
Anti-Patterns
// ❌ BAD: Duplicate titles
<title>Home</title> // Same on every page
// ❌ BAD: Keyword stuffing
<meta name="description" content="Buy shoes, shoes, best shoes, cheap shoes, red shoes" />
// ❌ BAD: No alt text
<img src="product.jpg" />
// ❌ BAD: Blocking resources
<link rel="stylesheet" href="heavy-style.css" />
<script src="heavy-script.js"></script>
// ❌ BAD: No schema markup
// Missing rich results opportunity
// ❌ BAD: Redirects chain
// /old-page → /newer-page → /current-page
// Use direct redirect instead
Integration with Project Standards
Enforces discoverability and performance:
- Improved search visibility (organic traffic)
- Better social sharing (engagement)
- Fast page loads (Core Web Vitals)
- Structured data (rich results)
Resources
- Next.js SEO: https://nextjs.org/learn/seo/introduction-to-seo
- Google Search Central: https://developers.google.com/search
- Schema.org: https://schema.org
- Lighthouse: https://developers.google.com/web/tools/lighthouse
Last Updated: January 24, 2026 Compatibility: Claude Opus 4.5, Claude Code v2.x Status: Production Ready
January 2026 Update: This skill is compatible with Claude Opus 4.5 and Claude Code v2.x. For complex tasks, use the
effort: highparameter for thorough analysis.
Didn't find tool you were looking for?