Marketplace seo-handler
Manage SEO, sitemaps, and metadata for optimal search engine visibility
install
source · Clone the upstream repo
git clone https://github.com/aiskillstore/marketplace
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/aiskillstore/marketplace "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/aayushbaniya2006/seo-handler" ~/.claude/skills/aiskillstore-marketplace-seo-handler && rm -rf "$T"
manifest:
skills/aayushbaniya2006/seo-handler/SKILL.mdsource content
SEO Handler Skill
This skill ensures your Next.js application is optimized for search engines. It covers sitemaps, metadata (OpenGraph), server-side rendering (SSR/ISR), structured data (JSON-LD), and robots.txt configuration.
Core Principles
- Sitemap Visibility: Every public page MUST be in
.src/app/sitemap.ts - Metadata Inheritance: Use
for defaults; override inlayout.tsx
.page.tsx - Structured Data: Use
components (JSON-LD) on every content page.next-seo - Server-Side Data: Fetch content-heavy data on the server (ISR) for SEO.
- Robots Control: Use
to guide crawlers.src/app/robots.ts
1. Sitemap Management
File:
src/app/sitemap.ts
When adding a new public route (e.g.,
/features), add it to the staticPages array.
const staticPages = [ "", // Home "/features", // New page // ... ];
Splitting Sitemaps (Large Sites)
If you have >50k URLs (e.g., user profiles, products), use
generateSitemaps.
File:
src/app/products/sitemap.ts
import { BASE_URL } from '@/lib/constants'; export async function generateSitemaps() { // Fetch total count and divide by batch size (e.g., 50,000) return [{ id: 0 }, { id: 1 }]; } export default async function sitemap({ id }: { id: { id: number } }) { const start = id.id * 50000; const products = await getProducts(start, 50000); return products.map(product => ({ url: `${BASE_URL}/products/${product.slug}`, lastModified: product.updatedAt, })); }
2. Metadata & OpenGraph
Base Layout:
src/app/layout.tsx (or group layout) defines the template.
export const metadata: Metadata = { title: { template: "%s | My App", default: "My App - The Best SaaS", }, description: "...", openGraph: { ... }, // Default OG };
Page Level:
src/app/blog/[slug]/page.tsx
export async function generateMetadata({ params }): Promise<Metadata> { const post = await getPost(params.slug); return { title: post.title, description: post.summary, openGraph: { images: [post.coverImage], // Overrides default }, }; }
3. Structured Data (JSON-LD)
Primary: Next-SEO Components
Use
next-seo components to inject JSON-LD structured data. This helps search engines understand your content (Articles, Products, FAQs, etc.).
Page Component:
src/app/blog/[slug]/page.tsx
import { ArticleJsonLd } from "next-seo"; export default function BlogPost({ post }) { return ( <> <ArticleJsonLd useAppDir url={`https://myapp.com/blog/${post.slug}`} title={post.title} images={[post.image]} datePublished={post.date} authorName="My App" description={post.description} /> <article>...</article> </> ); }
Alternative: Raw JSON-LD (Script Tag)
If a specific schema isn't supported by
next-seo, use a raw script tag. Wrap in Suspense if the data fetching is async.
import { Suspense } from "react"; export default function Page() { const jsonLd = { '@context': 'https://schema.org', '@type': 'CustomType', name: 'Custom Name', description: 'Description', }; return ( <section> <Suspense fallback={null}> <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} /> </Suspense> {/* Page Content */} </section> ); }
4. Incremental Static Regeneration (ISR)
For content pages (Blogs, Docs, Marketing) that rely on database content, use ISR to cache them at the edge.
Page Component:
// src/app/blog/[slug]/page.tsx // 1. Enable ISR export const revalidate = 3600; // Revalidate every hour (in seconds) // OR use dynamic params with generateStaticParams for full static export behavior export const dynamicParams = true; export async function generateStaticParams() { const posts = await db.query.posts.findMany(); return posts.map((post) => ({ slug: post.slug })); } export default async function BlogPost({ params }) { // 2. Fetch on Server (Data is cached based on revalidate config) const post = await getPost(params.slug); return <article>{/* ... */}</article>; }
5. Robots.txt configuration
File:
src/app/robots.ts
Ensure you disallow private routes (
/api/, /app/, /admin/) to save crawl budget.
export default function robots(): MetadataRoute.Robots { return { rules: { userAgent: "*", allow: "/", disallow: ["/api/", "/app/", "/super-admin/"], }, sitemap: `${process.env.NEXT_PUBLIC_APP_URL}/sitemap.xml`, }; }