**Version:** 2.0 (Next.js 16+ with MCP Integration) **Author:** FrankX AI Systems **Last Updated:** 2025-10-24
Scanned 5/27/2026
Install via CLI
openskills install frankxai/agentic-creator-os# 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+):**
```typescript
// 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):**
```typescript
// 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):**
```typescript
// 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:**
```typescript
// 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:**
```typescript
// 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:**
```typescript
// 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:**
```typescript
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:**
```typescript
// 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:**
```typescript
// 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:**
```typescript
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:**
```typescript
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:**
```typescript
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:**
```typescript
// 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:**
```typescript
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:**
```typescript
// 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:**
```typescript
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:**
```typescript
// 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:**
```typescript
// 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:**
```typescript
// 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:**
```typescript
// app/dashboard/loading.tsx
export default function Loading() {
return <div>Loading dashboard...</div>
}
```
### Error Handling
**Error Boundary:**
```typescript
// 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:**
```typescript
// 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):**
```typescript
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):**
```typescript
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+)
```typescript
// 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
- [Next.js Documentation](https://nextjs.org/docs)
- [React Server Components](https://react.dev/reference/rsc/server-components)
- [Next.js Examples](https://github.com/vercel/next.js/tree/canary/examples)
- [Vercel Platform Docs](https://vercel.com/docs)
## 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.
No comments yet. Be the first to comment!