skills/nextjs-seo/SKILL.md
Next.js App Router SEO optimization and auditing. Use when implementing or fixing SEO in a Next.js app — metadata and generateMetadata, viewport/themeColor, Open Graph and og/twitter images (file conventions + ImageResponse), web app manifest, favicons/icons, sitemap.xml, robots.txt, canonical URLs, hreflang/i18n alternates, JSON-LD structured data and rich results, Core Web Vitals (LCP/INP/CLS), AI search/GEO and AI crawler rules (GPTBot, OAI-SearchBot), or diagnosing Google indexing problems (Search Console, "Discovered/Crawled - currently not indexed"). Also use to run an SEO audit checklist. Not for general Next.js feature work unrelated to SEO.
npx skillsauth add laguagu/claude-code-nextjs-skills nextjs-seoInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Comprehensive SEO guide for Next.js App Router applications.
Run this checklist for any Next.js project:
curl https://your-site.com/robots.txtcurl https://your-site.com/sitemap.xml<title> and <meta name="description">application/ld+jsonimport type { Metadata, Viewport } from 'next';
// Viewport must be a separate export — `themeColor`, `colorScheme`, and
// `viewport` inside the `metadata` object are not supported.
export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
maximumScale: 5,
userScalable: true,
themeColor: [
{ media: '(prefers-color-scheme: light)', color: '#ffffff' },
{ media: '(prefers-color-scheme: dark)', color: '#0a0a0a' },
],
};
export const metadata: Metadata = {
metadataBase: new URL('https://your-site.com'),
title: {
default: 'Site Title - Main Keyword',
template: '%s | Site Name',
},
description: 'Compelling description with keywords (150-160 chars; Google typically displays this range)',
keywords: ['keyword1', 'keyword2', 'keyword3'],
openGraph: {
type: 'website',
locale: 'en_US',
url: 'https://your-site.com',
siteName: 'Site Name',
title: 'Site Title',
description: 'Description for social sharing',
images: [{ url: '/og-image.png', width: 1200, height: 630, alt: 'Site preview' }],
},
twitter: {
card: 'summary_large_image',
title: 'Site Title',
description: 'Description for Twitter',
images: ['/og-image.png'],
},
alternates: {
canonical: '/',
},
robots: {
index: true,
follow: true,
},
};
import type { MetadataRoute } from 'next';
export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = 'https://your-site.com';
return [
{
url: baseUrl,
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 1,
images: [`${baseUrl}/og-image.png`], // Image Sitemap entry
},
{
url: `${baseUrl}/about`,
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.8,
},
];
}
import type { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
const baseUrl = 'https://your-site.com';
return {
rules: [
{
userAgent: '*',
allow: '/',
disallow: ['/api/', '/admin/'],
// Do NOT disallow /_next/ — crawlers need render-critical CSS/JS
// Do NOT add bot-specific rules (Googlebot, Bingbot) unless overriding wildcard
},
],
sitemap: `${baseUrl}/sitemap.xml`,
};
}
hostwas omitted intentionally — it's a non-standard directive Google ignores. Use canonical URLs / 301s to declare the preferred host instead. See references/sitemap-robots.md.
import type { MetadataRoute } from 'next';
export default function manifest(): MetadataRoute.Manifest {
return {
name: 'Site Name',
short_name: 'Site',
description: 'Site description',
start_url: '/',
display: 'standalone',
background_color: '#ffffff',
theme_color: '#0a0a0a',
icons: [
{ src: '/icon-192.png', sizes: '192x192', type: 'image/png' },
{ src: '/icon-512.png', sizes: '512x512', type: 'image/png' },
],
};
}
Same MetadataRoute family as sitemap/robots; place at the root of app/. Minor for ranking, but expected for PWA completeness. (A static app/manifest.json works too.)
Three ways to set social images — prefer the file conventions over hand-syncing URLs in the metadata object:
openGraph.images / twitter.images examples above) — fine for externally hosted images.opengraph-image.(png|jpg|gif) and/or twitter-image.* into a route segment (app/opengraph-image.png for the root, app/blog/opengraph-image.png for /blog). Next.js auto-emits og:image/twitter:image + :type/:width/:height. A deeper, more specific image overrides one above it. Add alt text with a sibling opengraph-image.alt.txt. Build fails if the file exceeds 8 MB (OG) / 5 MB (Twitter).ImageResponse (per-page/per-post images):// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og';
export const alt = '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; // params is a Promise in v16
const post = await getPost(slug);
return new ImageResponse(
<div style={{ display: 'flex', fontSize: 64, width: '100%', height: '100%' }}>{post.title}</div>,
{ ...size },
);
}
ImageResponse renders via Satori — flexbox only, no display: grid. These files are statically optimized at build time unless they read request-time data. See references/metadata-api.md for fonts, generateImageMetadata, and the favicon/icon.tsx/apple-icon conventions.
With cacheComponents: true in next.config.ts (the v16 top-level flag that unifies the old experimental.dynamicIO/ppr/useCache), use the "use cache" directive for SEO-critical server components:
// app/(home)/sections/hero-section.tsx
import { cacheLife, cacheTag } from "next/cache";
export async function HeroSection() {
"use cache";
cacheLife("hours"); // SEO content that changes a few times/day; see profiles below
cacheTag("hero"); // Invalidate via updateTag("hero") in a Server Action
const data = await fetchData();
return <div>{/* SEO-visible content */}</div>;
}
Built-in cacheLife profiles (stale / revalidate / expire): seconds (30s/1s/1m), minutes (5m/1m/1h), hours (5m/1h/1d), days (5m/1d/1w), weeks (5m/1w/30d), max (5m/30d/1y), and the implicit default (5m/15m/never). For SEO pages pick by how often content changes — days for blog/docs, max for legal/marketing. (minutes revalidates every 1 min — too aggressive for most SEO content.)
Key rules:
"use cache" must be the first statement in the function body (or at the top of the file for file-level caching)cookies()/headers()/searchParams inside a plain "use cache" scope — good for SEO, since indexable content should be request-agnostic. ("use cache: private" does allow them, but is never prerendered, so it never lands in the static SEO shell.)updateTag("hero") inside a Server Action (read-your-writes), or revalidateTag("hero") from a Route Handler / webhook — prefer these over export const revalidateseconds, or revalidate < 5 min) are excluded from the prerender and become dynamic holes that need a <Suspense> boundary — keep SEO-critical content on a longer profile so it stays in the static shell"use cache" (+ cacheTag) if they fetch CMS/dynamic data you want to invalidate on publish| Strategy | Use When | SEO Impact | |----------|----------|------------| | "use cache" | Server components with periodic data | Best - cached HTML, fast TTFB | | SSG (Static) | Content rarely changes | Best - pre-rendered HTML | | SSR | Dynamic content per request | Great - server-rendered | | CSR | Dashboards, authenticated areas | Poor - avoid for SEO pages |
| Metric | Target | Impact | |--------|--------|--------| | LCP (Largest Contentful Paint) | < 2.5s | Loading speed | | INP (Interaction to Next Paint) | < 200ms | Interactivity | | CLS (Cumulative Layout Shift) | < 0.1 | Visual stability |
Metadata + CWV alone don't drive rankings. Keep these in mind (out of scope for this skill, but pointers):
alternates.canonical/_next/ in robots.txt - Crawlers need render-critical CSS/JS; never disallow /_next/favicon.ico/icon.*/opengraph-image.* file conventions; they auto-emit tags and override the metadata objectGPTBot disallow: / blocks training but leaves you in AI search; don't accidentally block citation bots (OAI-SearchBot, PerplexityBot). See references/ai-search.mdexport const metadata: Metadata = {
robots: {
index: false,
follow: false,
},
};
type Props = { params: Promise<{ id: string }> };
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { id } = await params; // params is a Promise in current Next.js
const product = await getProduct(id);
return {
title: product.name,
description: product.description,
};
}
type Props = { params: Promise<{ slug: string }> };
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
return {
alternates: {
canonical: `/products/${slug}`,
},
};
}
testing
Creates new skills, modifies and improves existing skills, and measures skill performance. Use when users want to create a skill from scratch, update or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.
development
PostgreSQL-based semantic and hybrid search with pgvector and ParadeDB. Use when implementing vector search, semantic search, hybrid search, or full-text search in PostgreSQL. Covers pgvector indexing, hybrid FTS/BM25 + RRF, ParadeDB, reranking, halfvec, multilingual search, query translation, and domain evals. Triggers: pgvector, vector search, semantic search, hybrid search, embedding search, PostgreSQL RAG, BM25, RRF, HNSW index, similarity search, ParadeDB, pg_search, reranking, Cohere rerank, Voyage rerank, graceful fallback, iterative_scan, filtered HNSW, websearch_to_tsquery, unaccent, multilingual FTS, pg_trgm, trigram, fuzzy search, LIKE, ILIKE, autocomplete, typo tolerance, fuzzystrmatch, evaluation, benchmarking, Hit@K, MRR, halfvec cast, cross-lingual retrieval, non-English corpus, per-language indexing, query translation, RRF fusion across languages
tools
OpenAI Agents SDK (Python) development. Use when building AI agents, multi-agent handoffs, function tools, guardrails, sessions, streaming, or tracing with the `openai-agents` / `agents` Python package — including Azure OpenAI via LiteLLM. Triggers on imports from `agents`, uses of `Runner.run_sync`/`Runner.run_streamed`, `@function_tool`, `AgentOutputSchema`, `SQLiteSession`, or questions about the openai-agents-python SDK.
development
Creates Next.js frontends with shadcn/ui. Use when building React UIs, components, pages, or applications with shadcn, Tailwind, or modern frontend patterns. Also use when the user asks to create a new Next.js project, add UI components, style pages, or build any web interface — even if they don't mention shadcn explicitly.