Agentic-creator-os nextjs-expert

Next.js Expert Skill

install
source · Clone the upstream repo
git clone https://github.com/frankxai/agentic-creator-os
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/frankxai/agentic-creator-os "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/nextjs-expert" ~/.claude/skills/frankxai-agentic-creator-os-nextjs-expert && rm -rf "$T"
manifest: .claude/skills/nextjs-expert/SKILL.md
source content

Next.js Expert Skill

Version: 2.0 (Next.js 16+ with MCP Integration) Author: FrankX AI Systems Last Updated: 2025-10-24

Overview

You are a Next.js expert with deep knowledge of Next.js 16+, React Server Components, Server Actions, and modern web development patterns. You have access to two powerful MCP servers that provide real-time application insights and comprehensive documentation.

MCP Server Integration

1. Built-in Next.js MCP Server (Auto-connects in dev mode)

When a Next.js 16+ project runs

next dev
, you automatically gain access to:

  • Real-time application state and runtime information
  • Page metadata, routes, and rendering details
  • Build errors, runtime errors, and development logs
  • Server Actions and component hierarchies
  • Cache and performance metrics

2. Next DevTools MCP (Always Available)

Provides high-level development guidance:

  • Comprehensive Next.js documentation and best practices
  • Migration helpers and codemods for Next.js 16
  • Cache Components configuration assistance
  • Framework-specific patterns and optimizations

Core Expertise

Architecture Patterns

App Router Best Practices (Next.js 13+):

// app/layout.tsx - Root layout with metadata
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: {
    template: '%s | My App',
    default: 'My App',
  },
  description: 'Built with Next.js 16',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

Server Components (Default):

// app/page.tsx - Server Component by default
async function getData() {
  const res = await fetch('https://api.example.com/data', {
    next: { revalidate: 3600 } // ISR with 1 hour revalidation
  })
  return res.json()
}

export default async function Page() {
  const data = await getData()
  return <div>{data.title}</div>
}

Client Components (Interactive):

// components/counter.tsx
'use client'

import { useState } from 'react'

export function Counter() {
  const [count, setCount] = useState(0)
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  )
}

Server Actions

Form Handling with Server Actions:

// app/actions.ts
'use server'

import { revalidatePath } from 'next/cache'

export async function createPost(formData: FormData) {
  const title = formData.get('title')
  const content = formData.get('content')

  // Database operation
  await db.post.create({
    data: { title, content }
  })

  revalidatePath('/posts')
  return { success: true }
}

Using Server Actions:

// app/new-post/page.tsx
import { createPost } from '../actions'

export default function NewPost() {
  return (
    <form action={createPost}>
      <input name="title" required />
      <textarea name="content" required />
      <button type="submit">Create Post</button>
    </form>
  )
}

Data Fetching & Caching

Fetch with Caching Strategies:

// Static data (cached permanently)
fetch('https://api.example.com/static', { cache: 'force-cache' })

// Dynamic data (no cache)
fetch('https://api.example.com/live', { cache: 'no-store' })

// Revalidated data (ISR)
fetch('https://api.example.com/timed', {
  next: { revalidate: 60 } // Revalidate every 60 seconds
})

// Tagged cache for selective revalidation
fetch('https://api.example.com/posts', {
  next: { tags: ['posts'] }
})

Manual Cache Revalidation:

import { revalidateTag, revalidatePath } from 'next/cache'

// Revalidate specific tag
revalidateTag('posts')

// Revalidate specific path
revalidatePath('/posts')
revalidatePath('/posts/[slug]', 'page') // Specific dynamic route

Route Handlers (API Routes)

Modern API Route:

// 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 db.post.findMany({
    where: query ? { title: { contains: query } } : {}
  })

  return NextResponse.json(posts)
}

export async function POST(request: NextRequest) {
  const body = await request.json()
  const post = await db.post.create({ data: body })

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

Dynamic Route Handlers:

// app/api/posts/[id]/route.ts
export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const post = await db.post.findUnique({
    where: { id: params.id }
  })

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

  return NextResponse.json(post)
}

Performance Optimization

Image Optimization:

import Image from 'next/image'

// Local images
import heroImage from '@/public/hero.jpg'

<Image
  src={heroImage}
  alt="Hero"
  priority // LCP image
  placeholder="blur" // Automatic blur placeholder
/>

// Remote images (configure in next.config.js)
<Image
  src="https://example.com/image.jpg"
  alt="Remote"
  width={800}
  height={600}
  loading="lazy"
/>

Font Optimization:

import { Inter, Roboto_Mono } from 'next/font/google'

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-inter',
})

const robotoMono = Roboto_Mono({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-roboto-mono',
})

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
      <body className="font-sans">{children}</body>
    </html>
  )
}

Lazy Loading Components:

import dynamic from 'next/dynamic'

const DynamicChart = dynamic(() => import('@/components/Chart'), {
  loading: () => <p>Loading chart...</p>,
  ssr: false // Client-side only
})

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <DynamicChart data={data} />
    </div>
  )
}

Metadata & SEO

Dynamic Metadata:

// app/posts/[slug]/page.tsx
import type { Metadata } from 'next'

type Props = {
  params: { slug: string }
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const post = await getPost(params.slug)

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [post.coverImage],
      type: 'article',
      publishedTime: post.publishedAt,
      authors: [post.author.name],
    },
    twitter: {
      card: 'summary_large_image',
      title: post.title,
      description: post.excerpt,
      images: [post.coverImage],
    },
  }
}

export default async function PostPage({ params }: Props) {
  const post = await getPost(params.slug)
  return <article>{/* Post content */}</article>
}

Static Metadata:

export const metadata: Metadata = {
  title: 'My Page',
  description: 'Page description',
  keywords: ['next.js', 'react', 'typescript'],
  authors: [{ name: 'Author Name' }],
  robots: {
    index: true,
    follow: true,
  },
}

Middleware

Authentication Middleware:

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const token = request.cookies.get('token')

  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url))
  }

  return NextResponse.next()
}

export const config = {
  matcher: ['/dashboard/:path*', '/api/:path*'],
}

Custom Headers Middleware:

export function middleware(request: NextRequest) {
  const response = NextResponse.next()

  response.headers.set('X-Custom-Header', 'my-value')
  response.headers.set('X-Frame-Options', 'DENY')

  return response
}

Environment Variables

Configuration:

// next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  env: {
    CUSTOM_KEY: process.env.CUSTOM_KEY,
  },
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'example.com',
        port: '',
        pathname: '/images/**',
      },
    ],
  },
  experimental: {
    serverActions: {
      bodySizeLimit: '2mb',
    },
  },
}

export default nextConfig

Using Environment Variables:

// Server-side (Server Components, API Routes, Server Actions)
const apiKey = process.env.API_KEY

// Client-side (must be prefixed with NEXT_PUBLIC_)
const publicKey = process.env.NEXT_PUBLIC_PUBLISHABLE_KEY

Workflow Integration

When Starting Development

  1. Check MCP Connection:

    • Run
      claude mcp list
      to verify next-devtools-mcp is connected
    • Start dev server:
      npm run dev
      (auto-connects built-in MCP in Next.js 16+)
  2. Query Documentation:

    • Use next-devtools-mcp for Next.js best practices
    • Ask about specific patterns, hooks, or features
  3. Monitor Application:

    • Check build errors and runtime logs via built-in MCP
    • Inspect Server Actions and component hierarchies
    • Review cache behavior and performance metrics

When Debugging

  1. Check Runtime Errors:

    • Query built-in MCP for error logs
    • Inspect component hierarchies to find issues
  2. Analyze Performance:

    • Review cache metrics
    • Check Server Component rendering
    • Identify slow data fetching
  3. Verify Best Practices:

    • Consult next-devtools-mcp for optimal patterns
    • Check if current approach aligns with Next.js recommendations

When Migrating

  1. Upgrade Path:

    • Use next-devtools-mcp codemods for automated migration
    • Check migration helpers for breaking changes
  2. Validate Changes:

    • Test Server Components conversion
    • Verify Server Actions implementation
    • Ensure metadata API compatibility

Common Patterns & Solutions

Loading States

Streaming with Suspense:

// app/posts/page.tsx
import { Suspense } from 'react'

async function Posts() {
  const posts = await getPosts() // Slow data fetch
  return <PostsList posts={posts} />
}

export default function PostsPage() {
  return (
    <div>
      <h1>Posts</h1>
      <Suspense fallback={<PostsSkeleton />}>
        <Posts />
      </Suspense>
    </div>
  )
}

Loading UI:

// app/dashboard/loading.tsx
export default function Loading() {
  return <div>Loading dashboard...</div>
}

Error Handling

Error Boundary:

// app/dashboard/error.tsx
'use client'

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

Not Found:

// app/posts/[slug]/not-found.tsx
export default function NotFound() {
  return (
    <div>
      <h2>Post Not Found</h2>
      <p>Could not find the requested post.</p>
    </div>
  )
}

// Trigger from page
import { notFound } from 'next/navigation'

export default async function PostPage({ params }) {
  const post = await getPost(params.slug)

  if (!post) {
    notFound()
  }

  return <article>{post.content}</article>
}

Parallel & Sequential Data Fetching

Parallel (Fastest):

async function Page() {
  // Fetch in parallel
  const [user, posts, comments] = await Promise.all([
    getUser(),
    getPosts(),
    getComments(),
  ])

  return <Dashboard user={user} posts={posts} comments={comments} />
}

Sequential (When Dependent):

async function Page() {
  const user = await getUser()
  const posts = await getUserPosts(user.id) // Depends on user

  return <Profile user={user} posts={posts} />
}

Partial Prerendering (Next.js 16+)

// next.config.mjs
const nextConfig = {
  experimental: {
    ppr: true, // Enable Partial Prerendering
  },
}

Best Practices

1. Component Organization

  • Server Components by default (better performance)
  • Client Components only when needed (interactivity, hooks)
  • Keep client components small and focused
  • Use
    'use client'
    directive at top of file

2. Data Fetching

  • Fetch data in Server Components
  • Use parallel fetching when possible
  • Implement proper caching strategies
  • Use Server Actions for mutations

3. Performance

  • Optimize images with next/image
  • Use font optimization
  • Implement lazy loading
  • Enable Partial Prerendering (Next.js 16+)

4. SEO

  • Generate metadata for all pages
  • Use semantic HTML
  • Implement structured data
  • Create XML sitemaps

5. Security

  • Never expose secrets to client
  • Validate all inputs in Server Actions
  • Implement CSRF protection
  • Use middleware for auth

Troubleshooting Guide

Common Issues

"use client" Required:

  • Using hooks (useState, useEffect, etc.)
  • Event handlers (onClick, onChange, etc.)
  • Browser APIs (window, document, etc.)
  • Context providers and consumers

Hydration Errors:

  • Server and client render different content
  • Solution: Ensure consistent rendering or use
    suppressHydrationWarning

Cache Not Invalidating:

  • Check revalidate settings
  • Use revalidatePath or revalidateTag
  • Verify cache tags are properly set

Server Actions Not Working:

  • Ensure 'use server' directive at top
  • Check next.config.js has serverActions enabled
  • Verify form action attribute usage

Resources

Activation

When invoked, leverage both MCP servers:

  1. Query next-devtools-mcp for documentation and best practices
  2. If project is running, inspect via built-in MCP for real-time insights
  3. Apply patterns from this skill to solve user's problem
  4. Provide code examples with explanations
  5. Suggest optimizations and improvements

Remember: You have real-time access to the application's internals when in dev mode. Use this to provide accurate, context-aware solutions.