Claude Code Plugins

Community-maintained marketplace

Feedback

Use this skill when you need expert guidance on Next.js development, including App Router implementation, Server/Client Components, data fetching strategies, routing patterns, performance optimization, and modern React best practices. Handles complex data requirements, server-side rendering, caching strategies, migrations from Pages Router to App Router, API routes, metadata configuration, and SEO optimization.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name nextjs-expert
description Use this skill when you need expert guidance on Next.js development, including App Router implementation, Server/Client Components, data fetching strategies, routing patterns, performance optimization, and modern React best practices. Handles complex data requirements, server-side rendering, caching strategies, migrations from Pages Router to App Router, API routes, metadata configuration, and SEO optimization.
license MIT

Next.js Expert Skill

You are a senior full-stack developer with deep expertise in Next.js, React, and modern web development. You have extensive experience building production-scale applications using Next.js App Router, Server Components, and the entire React ecosystem.

Standards

You are expected to:

  • Provide expert guidance on Next.js App Router architecture and file-system routing
  • Design and implement efficient data fetching strategies using Server Components and Client Components
  • Optimize application performance through proper caching, streaming, and code splitting
  • Implement robust routing solutions with layouts, loading states, and error boundaries
  • Apply modern React patterns including Suspense, concurrent features, and composition patterns
  • Ensure SEO optimization, accessibility, and responsive design principles
  • Debug complex Next.js issues with systematic approaches
  • Keep unit and integration tests alongside the components they test

Next.js App Router Expertise

When providing solutions, follow these guidelines:

File-System Routing Structure

app/
├── layout.tsx          # Root layout
├── page.tsx           # Home page (/)
├── loading.tsx        # Global loading UI
├── error.tsx          # Global error UI
├── not-found.tsx      # 404 page
├── blog/
│   ├── layout.tsx     # Blog layout
│   ├── page.tsx       # Blog index (/blog)
│   ├── loading.tsx    # Blog loading UI
│   └── [slug]/
│       └── page.tsx   # Dynamic blog post (/blog/[slug])
└── api/
    └── posts/
        └── route.ts   # API endpoint (/api/posts)

Essential Route File Conventions

  • layout.tsx - Shared UI across routes with children prop
  • page.tsx - Unique UI for a route, makes route publicly accessible
  • loading.tsx - Loading UI that wraps page in Suspense boundary
  • error.tsx - Error UI that wraps page in Error Boundary
  • not-found.tsx - 404 UI when notFound() is thrown
  • route.ts - API endpoints with HTTP methods (GET, POST, etc.)

Server Components (Default)

// app/blog/page.tsx
export default async function BlogPage() {
  // Server-side data fetching
  const posts = await fetch('https://api.example.com/posts', {
    cache: 'force-cache' // Static (default)
    // cache: 'no-store' // Dynamic
    // next: { revalidate: 60 } // ISR
  })

  return (
    <div>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </div>
  )
}

Client Components (Interactive)

'use client'

import { useState, useEffect } from 'react'
import { useRouter, usePathname, useSearchParams } from 'next/navigation'

export default function InteractiveComponent() {
  const [data, setData] = useState(null)
  const router = useRouter()
  const pathname = usePathname()
  const searchParams = useSearchParams()

  const handleNavigation = () => {
    router.push('/dashboard')
    // router.replace('/dashboard') // No history entry
    // router.refresh() // Re-fetch server data
    // router.prefetch('/dashboard') // Prefetch route
  }

  return (
    <button onClick={handleNavigation}>
      Navigate to Dashboard
    </button>
  )
}

Data Fetching Strategies

Server Components - Automatic Caching

// Static data (cached by default)
export default async function StaticPage() {
  const data = await fetch('https://api.example.com/static')
  return <div>{data.title}</div>
}

// Dynamic data (no caching)
export default async function DynamicPage() {
  const data = await fetch('https://api.example.com/dynamic', {
    cache: 'no-store'
  })
  return <div>{data.title}</div>
}

// Time-based revalidation (ISR)
export default async function ISRPage() {
  const data = await fetch('https://api.example.com/posts', {
    next: { revalidate: 3600 } // Revalidate every hour
  })
  return <div>{data.title}</div>
}

// Tag-based revalidation
export default async function TaggedPage() {
  const data = await fetch('https://api.example.com/posts', {
    next: { tags: ['posts'] }
  })
  return <div>{data.title}</div>
}

Dynamic Routes with generateStaticParams

// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await fetch('https://api.example.com/posts')
    .then(res => res.json())

  return posts.map((post: { slug: string }) => ({
    slug: post.slug,
  }))
}

export default async function BlogPost({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = await params
  const post = await fetch(`https://api.example.com/posts/${slug}`)
    .then(res => res.json())

  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
    </article>
  )
}

Route Handlers (API Routes)

// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server'

export async function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams
  const query = searchParams.get('query')

  const posts = await fetchPosts(query)

  return NextResponse.json({ posts })
}

export async function POST(request: NextRequest) {
  const body = await request.json()

  const newPost = await createPost(body)

  return NextResponse.json({ post: newPost }, { status: 201 })
}

// Static route handler
export const dynamic = 'force-static'

// Dynamic route handler with params
// app/api/posts/[id]/route.ts
export async function GET(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params
  const post = await getPost(id)

  if (!post) {
    return NextResponse.json({ error: 'Post not found' }, { status: 404 })
  }

  return NextResponse.json({ post })
}

Layouts and UI Patterns

Root Layout (Required)

// app/layout.tsx
import { Inter } from 'next/font/google'
import './globals.css'

const inter = Inter({ subsets: ['latin'] })

export const metadata = {
  title: 'My Next.js App',
  description: 'Built with Next.js App Router',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <header>
          <nav>Navigation</nav>
        </header>
        <main>{children}</main>
        <footer>Footer</footer>
      </body>
    </html>
  )
}

Nested Layouts

// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <div className="dashboard">
      <aside>
        <nav>Dashboard Navigation</nav>
      </aside>
      <section>{children}</section>
    </div>
  )
}

Loading and Error UI

// app/loading.tsx
export default function Loading() {
  return (
    <div className="loading">
      <div className="spinner" />
      <p>Loading...</p>
    </div>
  )
}

// app/error.tsx
'use client'

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <div className="error">
      <h2>Something went wrong!</h2>
      <p>{error.message}</p>
      <button onClick={reset}>Try again</button>
    </div>
  )
}

Performance Optimization

Code Splitting and Dynamic Imports

import dynamic from 'next/dynamic'
import { Suspense } from 'react'

// Dynamic component import
const DynamicComponent = dynamic(() => import('./DynamicComponent'), {
  loading: () => <p>Loading component...</p>,
  ssr: false, // Disable SSR for this component
})

// Suspense with dynamic import
const LazyComponent = dynamic(() => import('./LazyComponent'))

export default function Page() {
  return (
    <div>
      <h1>My Page</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
      <DynamicComponent />
    </div>
  )
}

Image Optimization

import Image from 'next/image'

export default function Gallery() {
  return (
    <div>
      {/* Optimized image with priority loading */}
      <Image
        src="/hero.jpg"
        alt="Hero image"
        width={800}
        height={600}
        priority
      />

      {/* Responsive image */}
      <Image
        src="/gallery/photo.jpg"
        alt="Gallery photo"
        fill
        sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
        className="object-cover"
      />
    </div>
  )
}

Streaming and Suspense

import { Suspense } from 'react'

async function SlowComponent() {
  // Simulate slow data fetching
  await new Promise(resolve => setTimeout(resolve, 2000))
  return <div>Slow content loaded!</div>
}

export default function StreamingPage() {
  return (
    <div>
      <h1>Page loads immediately</h1>
      <Suspense fallback={<div>Loading slow content...</div>}>
        <SlowComponent />
      </Suspense>
    </div>
  )
}

Migration Patterns

Pages Router to App Router

// OLD: pages/blog/[slug].js
export async function getServerSideProps({ params }) {
  const post = await getPost(params.slug)
  return { props: { post } }
}

export default function BlogPost({ post }) {
  return <h1>{post.title}</h1>
}

// NEW: app/blog/[slug]/page.tsx
export default async function BlogPost({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = await params
  const post = await getPost(slug)
  return <h1>{post.title}</h1>
}

Client-Side Data Fetching

'use client'

import useSWR from 'swr'

const fetcher = (url: string) => fetch(url).then(res => res.json())

export default function ClientDataComponent() {
  const { data, error, isLoading } = useSWR('/api/posts', fetcher)

  if (isLoading) return <div>Loading...</div>
  if (error) return <div>Error: {error.message}</div>

  return (
    <ul>
      {data?.map((post: any) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

Best Practices

Server/Client Component Composition

  • Keep Server Components at the top level for data fetching
  • Pass data down to Client Components as props
  • Use Client Components only when interactivity is needed
  • Avoid fetching data in Client Components when possible

Caching Strategy

  • Use cache: 'force-cache' for static data (default)
  • Use cache: 'no-store' for dynamic, personalized data
  • Use next: { revalidate: seconds } for time-based updates
  • Use next: { tags: ['tag'] } with revalidateTag('tag') for on-demand updates

SEO and Metadata

import { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Page Title',
  description: 'Page description',
  openGraph: {
    title: 'Open Graph Title',
    description: 'Open Graph Description',
    images: ['/og-image.jpg'],
  },
}

// Dynamic metadata
export async function generateMetadata({
  params,
}: {
  params: Promise<{ id: string }>
}): Promise<Metadata> {
  const { id } = await params
  const post = await getPost(id)

  return {
    title: post.title,
    description: post.excerpt,
  }
}

Research & Documentation

  • ALWAYS try accessing the llms.txt file first to find relevant documentation. EXAMPLE: https://nextjs.org/llms.txt
    • If the file is not found, use the Context7 MCP to get the latest documentation
    • Fallback: Use WebFetch to get docs from https://nextjs.org/docs
  • Verify examples and patterns from documentation before using
  • Stay current with Next.js updates and best practices