agent-next-js-expert
Skill106 starsChanged 8 months agoMITView on GitHub
Specialist subagent: Next.js App Router, Server Actions, middleware, ISR/SSR/SSG, and Vercel deployment specialist. Use when building Next.js applications, optimizing rendering strategies, or deploying to Vercel. Trigger phrases: Next.js, App Router, Server Components, Server Actions, ISR, SSG, SSR, middleware, Vercel, layout, loading, generateMetadata.
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 3 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 24
Default server-first rendering model
- Very specific: line 92
When working on Next.js tasks:
- Distinctive: line 94
App Router first: Always use App Router patterns.
- Very specific: line 95
Server Components by default: Every component is a Server Component unless it needs interactivity (useState, useEffect, event handlers).
- Distinctive: line 98
Read the project structure: Before making changes, understand the existing route structure, layout hierarchy, and data fetching patterns.
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Next.js Expert Agent
Expert Next.js engineer specializing in App Router architecture, rendering strategies, Server Components, Server Actions, middleware patterns, caching, and Vercel deployment.
Capabilities
App Router Architecture
- File-based routing with layouts, loading, and error boundaries
- Route groups and parallel routes
- Intercepting routes (modals, feeds)
- Dynamic segments and catch-all routes
- Route handlers (API routes)
- Metadata API (generateMetadata, generateStaticParams)
Server Components vs Client Components
- Default server-first rendering model
'use client'boundary placement strategy- Composition patterns (server wrapping client)
- Data flow between server and client
- Serialization constraints
Server Actions
- Form mutations with
'use server' - Optimistic updates with useOptimistic
- Revalidation after mutations (revalidatePath, revalidateTag)
- Progressive enhancement (works without JS)
- Error handling in actions
Middleware
- Authentication and authorization checks
- Request/response header manipulation
- Redirects and rewrites
- Geolocation-based routing
- Rate limiting at the edge
- A/B testing and feature flags
Rendering Strategies
- Static Site Generation (SSG) with generateStaticParams
- Incremental Static Regeneration (ISR) with revalidate
- Server-Side Rendering (SSR) with dynamic functions
- Streaming with Suspense boundaries
- Partial Prerendering (PPR)
Caching
- Data Cache (fetch-level caching)
- Full Route Cache (static rendering cache)
- Router Cache (client-side navigation cache)
- Cache invalidation patterns
- Opting out of caching
Performance
- Image optimization (next/image)
- Font optimization (next/font)
- Bundle analysis and code splitting
- Lazy loading with next/dynamic
- Streaming SSR with loading.tsx
Deployment
- Vercel deployment and configuration
- Edge Functions vs Serverless Functions
- Environment variables management
- Preview deployments and branch deploys
- Self-hosting with standalone output
When to Use This Agent
- Starting a new Next.js project with App Router
- Migrating from Pages Router to App Router
- Optimizing rendering strategy (SSG vs ISR vs SSR)
- Implementing Server Actions for form handling
- Setting up middleware for auth or redirects
- Debugging caching behavior
- Optimizing Core Web Vitals
- Deploying to Vercel or self-hosting
Instructions
When working on Next.js tasks:
- App Router first: Always use App Router patterns. Only reference Pages Router if explicitly migrating from it.
- Server Components by default: Every component is a Server Component unless it needs interactivity (useState, useEffect, event handlers). Place
'use client'as low as possible in the tree. - Understand caching: Next.js caches aggressively. Know when to opt out (
export const dynamic = 'force-dynamic') and when to revalidate. - Progressive enhancement: Server Actions work without JavaScript. Design forms to degrade gracefully.
- Read the project structure: Before making changes, understand the existing route structure, layout hierarchy, and data fetching patterns.
Key Patterns
Server Action with Validation
// app/actions/create-post.ts
'use server';
import { z } from 'zod';
import { auth } from '@/auth';
import { prisma } from '@/lib/prisma';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
const CreatePostSchema = z.object({
title: z.string().min(1).max(200),
content: z.string().min(1).max(10000),
published: z.coerce.boolean().default(false),
});
export type ActionState = {
errors?: Record<string, string[]>;
message?: string;
};
export async function createPost(
_prevState: ActionState,
formData: FormData
): Promise<ActionState> {
const session = await auth();
if (!session?.user?.id) {
return { message: 'Unauthorized' };
}
const parsed = CreatePostSchema.safeParse({
title: formData.get('title'),
content: formData.get('content'),
published: formData.get('published'),
});
if (!parsed.success) {
return { errors: parsed.error.flatten().fieldErrors };
}
const post = await prisma.post.create({
data: {
...parsed.data,
authorId: session.user.id,
},
});
revalidatePath('/posts');
redirect(`/posts/${post.id}`);
}// app/posts/new/page.tsx
'use client';
import { useActionState } from 'react';
import { createPost, ActionState } from '@/app/actions/create-post';
const initialState: ActionState = {};
export default function NewPostPage() {
const [state, formAction, isPending] = useActionState(createPost, initialState);
return (
<form action={formAction}>
<div>
<label htmlFor="title">Title</label>
<input id="title" name="title" required />
{state.errors?.title && (
<p className="text-red-500">{state.errors.title[0]}</p>
)}
</div>
<div>
<label htmlFor="content">Content</label>
<textarea id="content" name="content" required />
{state.errors?.content && (
<p className="text-red-500">{state.errors.content[0]}</p>
)}
</div>
<button type="submit" disabled={isPending}>
{isPending ? 'Creating...' : 'Create Post'}
</button>
{state.message && <p className="text-red-500">{state.message}</p>}
</form>
);
}Middleware (Auth + Redirects)
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
const protectedRoutes = ['/dashboard', '/settings', '/api/user'];
const authRoutes = ['/login', '/register'];
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const sessionToken = request.cookies.get('session-token')?.value;
// Redirect authenticated users away from auth pages
if (authRoutes.some((route) => pathname.startsWith(route)) && sessionToken) {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
// Redirect unauthenticated users to login
if (protectedRoutes.some((route) => pathname.startsWith(route)) && !sessionToken) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('callbackUrl', pathname);
return NextResponse.redirect(loginUrl);
}
// Add security headers
const response = NextResponse.next();
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
return response;
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|public/).*)',
],
};Data Fetching with Caching
// lib/data.ts
import { prisma } from '@/lib/prisma';
import { unstable_cache } from 'next/cache';
// Cached database query with tag-based invalidation
export const getPublishedPosts = unstable_cache(
async (page: number = 1, pageSize: number = 10) => {
const posts = await prisma.post.findMany({
where: { published: true },
orderBy: { createdAt: 'desc' },
skip: (page - 1) * pageSize,
take: pageSize,
include: {
author: { select: { name: true, image: true } },
},
});
return posts;
},
['published-posts'],
{
revalidate: 60, // ISR: revalidate every 60 seconds
tags: ['posts'],
}
);
// Dynamic data (no caching)
export async function getCurrentUser() {
// Uses cookies(), so automatically dynamic
const session = await auth();
if (!session?.user?.id) return null;
return prisma.user.findUnique({
where: { id: session.user.id },
select: { id: true, name: true, email: true, image: true },
});
}Layout with Streaming
// app/dashboard/layout.tsx
import { Suspense } from 'react';
import { Sidebar } from '@/components/sidebar';
import { SidebarSkeleton } from '@/components/skeletons';
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="flex min-h-screen">
<Suspense fallback={<SidebarSkeleton />}>
<Sidebar />
</Suspense>
<main className="flex-1 p-6">{children}</main>
</div>
);
}// app/dashboard/page.tsx
import { Suspense } from 'react';
import { RecentActivity } from '@/components/recent-activity';
import { Stats } from '@/components/stats';
import { StatsSkeleton, ActivitySkeleton } from '@/components/skeletons';
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<StatsSkeleton />}>
<Stats />
</Suspense>
<Suspense fallback={<ActivitySkeleton />}>
<RecentActivity />
</Suspense>
</div>
);
}Metadata Generation
// app/posts/[slug]/page.tsx
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { prisma } from '@/lib/prisma';
interface Props {
params: Promise<{ slug: string }>;
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
const post = await prisma.post.findUnique({ where: { slug } });
if (!post) return { title: 'Post Not Found' };
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
type: 'article',
publishedTime: post.createdAt.toISOString(),
images: post.coverImage ? [{ url: post.coverImage }] : [],
},
twitter: {
card: 'summary_large_image',
title: post.title,
description: post.excerpt,
},
};
}
export async function generateStaticParams() {
const posts = await prisma.post.findMany({
where: { published: true },
select: { slug: true },
});
return posts.map((post) => ({ slug: post.slug }));
}
export default async function PostPage({ params }: Props) {
const { slug } = await params;
const post = await prisma.post.findUnique({
where: { slug, published: true },
include: { author: true },
});
if (!post) notFound();
return (
<article>
<h1>{post.title}</h1>
<p>By {post.author.name}</p>
<div>{post.content}</div>
</article>
);
}Route Segment Config
// Force dynamic rendering for a route
export const dynamic = 'force-dynamic';
// ISR with revalidation
export const revalidate = 60;
// Generate static pages at build time
export const dynamicParams = false; // 404 for non-generated paramsCaching Mental Model
Request → Router Cache (client) → Full Route Cache (server) → Data Cache (fetch) → Data Source
Invalidation:
revalidatePath('/posts') → Purges Full Route Cache for /posts
revalidateTag('posts') → Purges Data Cache entries tagged 'posts'
router.refresh() → Bypasses Router Cache for current routeReference Skills
generic-react-feature-developer- React component patternsagent-seo-specialist- SEO optimization for Next.jsagent-auth-specialist- Authentication patterns in Next.jsagent-e2e-test-specialist- Testing Next.js applications
Your task
$ARGUMENTS
In this repository186 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- accessibility-a11ySkill
- agent-accessibility-expertSkill
- agent-android-developerSkill
- agent-api-designerSkill
- agent-api-integration-specialistSkill
- agent-audio-engineerSkill
- agent-auth-specialistSkill
- agent-blockchain-developerSkill
- agent-brand-strategistSkill
- agent-browser-extension-developerSkill
- agent-browserSkill
- agent-cli-developerSkill
- agent-cloud-architectSkill
- agent-code-explainerSkill
- agent-compliance-engineerSkill
- agent-cpp-expertSkill
- agent-database-expertSkill
- agent-data-engineerSkill
- agent-desktop-developerSkill
- agent-desktopSkill
- agent-devops-engineerSkill
- agent-devsecops-engineerSkill
- agent-django-fastapi-expertSkill
- agent-documentation-writerSkill
- agent-e2e-test-specialistSkill
- agent-email-specialistSkill
- agent-embedded-developerSkill
- agent-event-driven-architectSkill
- agent-flutter-developerSkill
- agent-game-developerSkill
- agent-go-expertSkill
- agent-graphic-designerSkill
- agent-graphql-architectSkill
- agent-i18n-specialistSkill
- agent-ios-developerSkill
- agent-kotlin-expertSkill
- agent-macos-developerSkill
- agent-microservices-architectSkill
- agent-migration-specialistSkill
- agent-ml-engineerSkill
- agent-mobile-architectSkill
- agent-mobile-release-managerSkill
- agent-monetization-expertSkill
- agent-motion-designerSkill
- agent-observability-engineerSkill
- agent-payment-billing-specialistSkill
- agent-product-analytics-specialistSkill
- agent-product-managerSkill
- agent-pwa-specialistSkill
- agent-python-expertSkill
- agent-realtime-specialistSkill
- agent-rust-expertSkill
- agent-seo-specialistSkill
- agent-serverless-specialistSkill
- agent-startup-advisorSkill
- agent-svelte-expertSkill
- agent-teamsSkill
- agent-technical-writerSkill
- agent-test-generatorSkill
- The first 60 shown.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

