git clone https://github.com/frankxai/agentic-creator-os
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"
.claude/skills/nextjs-expert/SKILL.mdNext.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
-
Check MCP Connection:
- Run
to verify next-devtools-mcp is connectedclaude mcp list - Start dev server:
(auto-connects built-in MCP in Next.js 16+)npm run dev
- Run
-
Query Documentation:
- Use next-devtools-mcp for Next.js best practices
- Ask about specific patterns, hooks, or features
-
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
-
Check Runtime Errors:
- Query built-in MCP for error logs
- Inspect component hierarchies to find issues
-
Analyze Performance:
- Review cache metrics
- Check Server Component rendering
- Identify slow data fetching
-
Verify Best Practices:
- Consult next-devtools-mcp for optimal patterns
- Check if current approach aligns with Next.js recommendations
When Migrating
-
Upgrade Path:
- Use next-devtools-mcp codemods for automated migration
- Check migration helpers for breaking changes
-
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
directive at top of file'use client'
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:
- Query next-devtools-mcp for documentation and best practices
- If project is running, inspect via built-in MCP for real-time insights
- Apply patterns from this skill to solve user's problem
- Provide code examples with explanations
- 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.