nextjs
Skill0 starsChanged 3 months agoMITView on GitHub
Build Next.js 16+ applications with App Router, server components, caching strategies, and production deployment patterns
- Reads credentials
- Installs packages
39 other projects use a version of this, 1 of them edited.
Adopted by
Most recently taken up by
- RuneStone-Tech/optimize-anythingtheir own version, first seen Nov 8, 2026
- LuisSambrano/antigravity-configtheir own version, first seen Nov 8, 2026
- davidvornholt/mail-mcptheir own version, first seen Nov 5, 2026
- davidvornholt/mailboxtheir own version, first seen Nov 5, 2026
- hmj1026/dhpktheir own version, first seen Nov 1, 2026
- thongdn-it/react-agent-skillstheir own version, first seen Nov 1, 2026
Used with
Projects with it also use nuqs, msw and 5 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
thongdn-itSkillnuqs
nuqs (type-safe URL query state) best practices for Next.js applications. This skill should be used when writing, reviewing, or refactoring code that uses nuqs for URL state management. Triggers on tasks involving useQueryState, useQueryStates, search params, URL state, query parameters, nuqs parsers, or Next.js routing with state.
Also in 3 projects that use this
thongdn-itSkillmsw
MSW (Mock Service Worker) best practices for API mocking in tests (formerly test-msw). This skill should be used when setting up MSW, writing request handlers, or mocking HTTP APIs. This skill does NOT cover general testing patterns (use test-vitest or test-tdd skills) or test methodology.
Also in 3 projects that use this
VercelSkillreact
React renderer for json-render that turns JSON specs into React components. Use when working with @json-render/react, building React UIs from JSON, creating component catalogs, or rendering AI-generated specs.
Also in 10 projects that use this
rawvegSkilllaravel
Laravel v12 - The PHP Framework For Web Artisans
Also in 3 projects that use this
MicrosoftSkillrust
Use this guide when working on Rust changes in the FAST monorepo.
Also in 3 projects that use this
- +2 more from thongdn-it
Other versions39 projects use a version of this.
Word for word · 0
None found.
Edited · 1
- ffsshhttiikk/opencode-agents-skills★ 212% same
Wrote their own version · 38
- openai/plugins★ 7.8ksame name
- udecode/better-convex★ 452same name
- leonvanzyl/agentic-coding-starter-kit★ 447same name
- kid-sid/claude-spellbook★ 190same name
- faqndo97/ai-skills★ 33same name
- nimadorostkar/Claude-Skills-collection★ 33same name
- PyModel/react-frontend-skills★ 23same name
- G1Joshi/Agent-Skills★ 15same name
- ulpi-io/browse★ 15same name
- wpank/ai★ 11same name
28 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository27 other files it gives its agents.
- architecture-designerSkill
- architecture-patternsSkill
- code-reviewSkill
- dockerSkill
- fastapiSkill
- feature-specificationSkill
- github-issue-resolverSkill
- github-opsSkill
- kubernetes-devopsSkill
- monitoringSkill
- nginxSkill
- prd-to-ddd-designSkill
- prd-writerSkill
- prismaSkill
- react-expertSkill
- redisSkill
- redis-storeSkill
- requirements-analysisSkill
- shadcn-uiSkill
- ssh-essentialsSkill
- task-decomposerSkill
- task-development-workflowSkill
- trpc-best-practicesSkill
- typescript-proSkill
- typescriptSkill
- webhookSkill
- zodSkill
The file
We split this skill into the rules it gives an agent
11 rules. 0 are common, 9 are distinctive, and 2 are very specific to this project.
0
Common
Most of the 35 edited versions we read keep these.
9
Distinctive
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 25
Use this skill when:
- Distinctive: line 37
Don't use: when you just need client-side React (use Vite), static site generation only (consider Astro), or non-React frameworks.
- Distinctive: line 39
Karpathy Principle: Think Before Coding: - Before scaffolding routes, map out your data flow: what needs SSR vs SSG vs client-side?
- Very specific: line 136
Karpathy Principle: Simplicity First: - Default to Server Components.
- Distinctive: line 784
Before deploying to production:
- Very specific: line 828
Use next.config.ts instead of .js
- Distinctive: line 834
Use Prisma in Server Components and Server Actions
- Distinctive: line 835
Never import Prisma in Client Components
- Distinctive: line 843
Use Server Components for layouts/static content
- Distinctive: line 848
Use tRPC for type-safe API layer instead of Route Handlers
- Distinctive: line 853
All React 18+ features supported (Suspense, Transitions, etc.)
“Common” is measured: kept by at least 6 in 10 of the 35 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Next.js - Production-Grade Full-Stack React Framework
When to Use
Use this skill when:
- Building modern full-stack React applications with server-side rendering
- Implementing complex routing with nested layouts and parallel routes
- Optimizing data fetching with Server Components and streaming
- Setting up production-ready caching strategies (ISR, PPR, static generation)
- Deploying Next.js apps to Vercel, Docker, or custom Node.js environments
- Migrating from Pages Router to App Router
- Implementing authentication flows with middleware and server actions
- Building API endpoints with Route Handlers
- Optimizing images, fonts, and static assets for production
Don't use when you just need client-side React (use Vite), static site generation only (consider Astro), or non-React frameworks.
Karpathy Principle: Think Before Coding - Before scaffolding routes, map out your data flow: what needs SSR vs SSG vs client-side? Where are mutations happening? This prevents expensive refactors later.
Prerequisites
Required Knowledge
- React 18+ fundamentals (components, hooks, props)
- JavaScript/TypeScript ES6+ (async/await, modules, destructuring)
- HTTP fundamentals (GET/POST, headers, cookies, status codes)
- Basic understanding of server vs client rendering
Required Tools
# Node.js 18.17+ or 20+
node --version # Should be >= 18.17
# Package manager (choose one)
npm --version # 9+
pnpm --version # 8+
yarn --version # 1.22+
# Create new Next.js project
npx create-next-app@latest my-app --typescript --tailwind --app
cd my-app
# Or clone existing project
git clone <repo>
cd <repo>
npm install
npm run dev # Start dev server on localhost:3000Project Structure (App Router)
app/
├── layout.tsx # Root layout (wraps all pages)
├── page.tsx # Home page (/)
├── loading.tsx # Loading UI for Suspense
├── error.tsx # Error boundary
├── not-found.tsx # 404 page
├── api/
│ └── route.ts # API Route Handler
├── blog/
│ ├── page.tsx # /blog
│ └── [slug]/
│ └── page.tsx # /blog/:slug
public/ # Static assets
components/ # Reusable components
lib/ # Utilities, database, etc.Core Workflows
1. Server vs Client Components
Default: Everything is a Server Component (runs on server, not sent to browser)
// app/page.tsx - Server Component (default)
import { db } from '@/lib/db'
export default async function HomePage() {
// ✅ Fetch directly in component
const posts = await db.post.findMany()
return (
<div>
<h1>Posts</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
</article>
))}
</div>
)
}Client Components: Add 'use client' for interactivity
// components/counter.tsx - Client Component
'use client'
import { useState } from 'react'
export function Counter() {
// ✅ Can use hooks, event handlers, browser APIs
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
)
}Karpathy Principle: Simplicity First - Default to Server Components. Only use 'use client' when you need hooks, event handlers, or browser APIs. Less JavaScript = faster page loads.
Mixing Server and Client:
// app/page.tsx - Server Component
import { ClientForm } from '@/components/client-form'
export default async function Page() {
const data = await fetchData() // Server-side data fetching
return (
<div>
<h1>Server-rendered heading</h1>
{/* Pass server data as props to client component */}
<ClientForm initialData={data} />
</div>
)
}Important Rules:
- ❌ Can't import Server Component into Client Component directly
- ✅ Can pass Server Component as
childrenor props to Client Component - ❌ Client Components can't be
async - ✅ Server Components can be
asyncand await data
2. Data Fetching Patterns
Parallel Fetching (Fast):
// ✅ Good - fetches run in parallel
export default async function Page() {
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments()
])
return <Dashboard user={user} posts={posts} comments={comments} />
}Sequential Fetching (Slow - Avoid):
// ❌ Bad - each fetch waits for previous
export default async function Page() {
const user = await fetchUser() // 100ms
const posts = await fetchPosts() // waits 100ms, then 150ms
const comments = await fetchComments() // waits 250ms, then 200ms
// Total: 450ms instead of 200ms
}Streaming with Suspense:
// app/page.tsx
import { Suspense } from 'react'
import { Posts } from '@/components/posts'
import { Comments } from '@/components/comments'
export default function Page() {
return (
<div>
<h1>Dashboard</h1>
{/* Fast content shows immediately */}
<UserProfile />
{/* Slow content streams in when ready */}
<Suspense fallback={<PostsSkeleton />}>
<Posts />
</Suspense>
<Suspense fallback={<CommentsSkeleton />}>
<Comments />
</Suspense>
</div>
)
}Karpathy Principle: Goal-Driven Execution - Optimize for Time to First Byte and First Contentful Paint. Stream slow content, show fast content immediately.
3. Caching Strategies
Next.js caches aggressively by default. Understand the layers:
Fetch Cache (Default: Cached):
// Cached forever by default
const res = await fetch('https://api.example.com/data')
// Revalidate every 60 seconds (ISR)
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 60 }
})
// Never cache (always fresh)
const res = await fetch('https://api.example.com/data', {
cache: 'no-store'
})
// Revalidate by tag (on-demand)
const res = await fetch('https://api.example.com/data', {
next: { tags: ['posts'] }
})Route Segment Cache:
// app/page.tsx
// Static (generated at build time)
export const dynamic = 'force-static'
// Dynamic (rendered on each request)
export const dynamic = 'force-dynamic'
// Revalidate every hour
export const revalidate = 3600
// Export segment config at top of page/layoutRevalidation (Clear Cache):
// app/actions.ts
'use server'
import { revalidatePath, revalidateTag } from 'next/cache'
export async function createPost(data: FormData) {
await db.post.create({ /* ... */ })
// Revalidate specific path
revalidatePath('/blog')
// Revalidate by tag (all fetches with this tag)
revalidateTag('posts')
}4. Server Actions (Form Mutations)
Basic Server Action:
// app/actions.ts
'use server'
export async function createPost(formData: FormData) {
const title = formData.get('title') as string
const content = formData.get('content') as string
// Validate
if (!title || !content) {
return { error: 'Missing fields' }
}
// Mutate
await db.post.create({ data: { title, content } })
// Revalidate cache
revalidatePath('/blog')
// Redirect
redirect('/blog')
}Use in Client Component:
// components/post-form.tsx
'use client'
import { createPost } from '@/app/actions'
import { useActionState } from 'react'
import { useFormStatus } from 'react-dom'
export function PostForm() {
const [state, formAction, isPending] = useActionState(createPost, null)
return (
<form action={formAction}>
<input name="title" required />
<textarea name="content" required />
<SubmitButton />
{state?.error && <p className="error">{state.error}</p>}
</form>
)
}
function SubmitButton() {
const { pending } = useFormStatus()
return (
<button disabled={pending}>
{pending ? 'Saving...' : 'Save'}
</button>
)
}Karpathy Principle: Surgical Changes - Server Actions let you mutate data without building API routes. Use them for forms, not as a general API layer.
5. Route Handlers (API Routes)
// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server'
// GET /api/posts
export async function GET(request: NextRequest) {
const posts = await db.post.findMany()
return NextResponse.json(posts)
}
// POST /api/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 })
}
// Enable CORS
export async function OPTIONS(request: NextRequest) {
return new NextResponse(null, {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
})
}Dynamic Route Handler:
// app/api/posts/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server'
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ 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)
}6. Dynamic Routes and Static Generation
Dynamic Route:
// app/blog/[slug]/page.tsx
interface PageProps {
params: Promise<{ slug: string }>
searchParams: { [key: string]: string | string[] | undefined }
}
export default async function BlogPost({ params }: PageProps) {
const post = await db.post.findUnique({
where: { slug: params.slug }
})
if (!post) {
notFound() // Shows not-found.tsx
}
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
)
}Static Generation with generateStaticParams:
// app/blog/[slug]/page.tsx
// Generate static pages at build time
export async function generateStaticParams() {
const posts = await db.post.findMany()
return posts.map(post => ({
slug: post.slug
}))
}
// 404 for unknown slugs (default: render on-demand)
export const dynamicParams = falseMetadata (SEO):
// app/blog/[slug]/page.tsx
import { Metadata } from 'next'
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const post = await db.post.findUnique({
where: { slug: params.slug }
})
if (!post) return { title: 'Not Found' }
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.coverImage],
},
}
}7. Middleware (Edge Runtime)
// middleware.ts (root of project)
import { NextRequest, NextResponse } from 'next/server'
export function middleware(request: NextRequest) {
// Check auth
const token = request.cookies.get('token')
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url))
}
// Add custom header
const response = NextResponse.next()
response.headers.set('x-custom-header', 'value')
return response
}
// Run on specific paths only
export const config = {
matcher: [
'/dashboard/:path*',
'/api/:path*',
// Exclude static files
'/((?!_next/static|_next/image|favicon.ico).*)',
]
}Important: Middleware runs on Edge, not Node.js - no fs, limited npm packages, no direct DB access.
Common Patterns
1. Layout Composition
// app/layout.tsx - Root layout
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Header />
<main>{children}</main>
<Footer />
</body>
</html>
)
}
// app/dashboard/layout.tsx - Nested layout
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="dashboard">
<Sidebar />
<div className="content">{children}</div>
</div>
)
}2. Error Boundaries
// app/error.tsx - Client Component error boundary
'use client'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={reset}>Try again</button>
</div>
)
}3. Loading States
// app/loading.tsx - Automatic Suspense boundary
export default function Loading() {
return <Spinner />
}
// Or use Suspense manually
<Suspense fallback={<Spinner />}>
<AsyncComponent />
</Suspense>4. Environment Variables
# .env.local (gitignored - secrets)
DATABASE_URL="postgresql://..."
JWT_SECRET="..."
# .env (committed - public config)
NEXT_PUBLIC_API_URL="https://api.example.com"// Server Component - access all env vars
const dbUrl = process.env.DATABASE_URL
// Client Component - only NEXT_PUBLIC_* vars
const apiUrl = process.env.NEXT_PUBLIC_API_URL5. Image Optimization
import Image from 'next/image'
// Local image (width/height inferred from import)
import logo from '@/public/logo.png'
export default function Page() {
return (
<>
{/* Local image */}
<Image src={logo} alt="Logo" />
{/* Remote image (width/height required) */}
<Image
src="https://example.com/photo.jpg"
alt="Photo"
width={500}
height={300}
/>
{/* Fill container (parent must be position: relative) */}
<div className="relative h-64">
<Image
src="/hero.jpg"
alt="Hero"
fill
className="object-cover"
/>
</div>
</>
)
}Configure allowed domains:
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com',
},
],
},
}
export default nextConfigCommon Pitfalls
1. ❌ Using Client-Only APIs in Server Components
// ❌ ERROR - window is not defined
export default async function Page() {
const width = window.innerWidth // Server Components can't access browser APIs
}
// ✅ CORRECT - extract to Client Component
'use client'
export function ClientComponent() {
const [width, setWidth] = useState(0)
useEffect(() => {
setWidth(window.innerWidth)
}, [])
}2. ❌ Importing Server Component into Client Component
// ❌ ERROR
'use client'
import { ServerComponent } from './server-component' // Can't import directly
// ✅ CORRECT - pass as children
export default function Page() {
return (
<ClientComponent>
<ServerComponent /> {/* Pass as children */}
</ClientComponent>
)
}3. ❌ Forgetting Revalidation After Mutations
// ❌ BAD - stale cache after mutation
'use server'
export async function deletePost(id: string) {
await db.post.delete({ where: { id } })
// Cache still shows deleted post!
}
// ✅ GOOD - revalidate cache
'use server'
export async function deletePost(id: string) {
await db.post.delete({ where: { id } })
revalidatePath('/blog') // Clear cache
}Karpathy Principle: Think Before Coding - Map your cache invalidation strategy before building. What paths/tags need revalidation after each mutation?
4. ❌ Over-Fetching in Loops
// ❌ BAD - N+1 query problem
export default async function Page() {
const users = await db.user.findMany()
return (
<>
{users.map(user => (
<UserCard key={user.id} user={user} />
))}
</>
)
}
// ❌ UserCard fetches posts for each user (N queries)
async function UserCard({ user }) {
const posts = await db.post.findMany({ where: { userId: user.id } })
return <div>{user.name}: {posts.length} posts</div>
}
// ✅ GOOD - fetch all data at once
export default async function Page() {
const users = await db.user.findMany({
include: { posts: true } // Single query with JOIN
})
return (
<>
{users.map(user => (
<UserCard key={user.id} user={user} posts={user.posts} />
))}
</>
)
}5. ❌ Not Handling Loading and Error States
// ❌ BAD - no loading or error UI
export default async function Page() {
const data = await fetchData()
return <div>{data.title}</div>
}
// ✅ GOOD - add loading.tsx and error.tsx
// app/loading.tsx
export default function Loading() {
return <Skeleton />
}
// app/error.tsx
'use client'
export default function Error({ error, reset }) {
return <ErrorMessage error={error} onRetry={reset} />
}6. ❌ Excessive Prefetching with <Link>
// ❌ BAD - prefetches all 100 links on page load
{posts.map(post => (
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
))}
// ✅ GOOD - disable prefetch for long lists
{posts.map(post => (
<Link href={`/blog/${post.slug}`} prefetch={false}>
{post.title}
</Link>
))}Verification Checklist
Before deploying to production:
Performance
- Run
npm run build- check bundle sizes, no errors - Verify Server Components are default (no unnecessary
'use client') - Image optimization configured (
next/imagefor all images) - Fonts optimized (
next/fontfor Google Fonts or local fonts) - Parallel data fetching (no sequential awaits in loops)
- Streaming with Suspense for slow content
Caching
- ISR configured for semi-static pages (
revalidate: N) - On-demand revalidation after mutations (
revalidatePath,revalidateTag) - Dynamic routes marked correctly (
dynamic = 'force-dynamic'where needed) - Cache headers correct for Route Handlers
SEO
- Metadata exported from pages/layouts (
generateMetadata) robots.txtandsitemap.xmlconfigured- Open Graph images set
- Canonical URLs for duplicate content
Error Handling
error.tsxin each route segmentnot-found.tsxfor 404s- Global error boundary (
app/global-error.tsx) - API error responses (4xx, 5xx with proper messages)
Security
- Environment variables not exposed to client (no
NEXT_PUBLIC_for secrets) - CORS configured for Route Handlers
- CSP headers set in
next.config.ts - Authentication middleware protecting routes
- Input validation in Server Actions
Build
- No build warnings or errors
- Output mode correct (
standalonefor Docker) .env.productionconfigured for production- Test build locally:
npm run build && npm run start
Integration with Other Skills
With TypeScript
- Use
next.config.tsinstead of.js - Type
paramsandsearchParamsin page components - Type Server Actions with Zod schema validation
With Prisma
- Initialize Prisma client in
lib/db.ts(singleton pattern) - Use Prisma in Server Components and Server Actions
- Never import Prisma in Client Components
With Zod
- Validate Server Action inputs with
zod - Parse
formDatawithzod-form-data - Type-safe form errors
With Shadcn/UI
- Use Server Components for layouts/static content
- Client Components for interactive UI (forms, dialogs, dropdowns)
- Combine Server Actions with shadcn forms
With tRPC
- Use tRPC for type-safe API layer instead of Route Handlers
- Server Components can call tRPC server-side
- Client Components use tRPC client
With React
- All React 18+ features supported (Suspense, Transitions, etc.)
- Server Components = React Server Components (RSC)
- Client Components = traditional React components
References
Official Documentation
Key Concepts
Deployment
Best Practices
Meta: Skill Quality
Karpathy Principle: Goal-Driven Execution - This skill prioritizes production-ready patterns over toy examples. Every workflow is designed to scale from prototype to production with minimal refactoring.
Completeness: 9/10 - Covers App Router, Server Components, caching, deployment, but doesn't cover PPR (Partial Prerendering) or advanced Middleware patterns.
Accuracy: 10/10 - Based on Next.js 16+ official docs and real-world production usage.
Practical Examples: 10/10 - All examples are copy-paste ready and follow current best practices.
Maintenance: Last updated April 2026 for Next.js 16+. Review quarterly as Next.js evolves rapidly.
Known Gaps:
- Advanced middleware patterns (geolocation, A/B testing, rate limiting)
- Partial Prerendering (PPR) - experimental feature
- React Server Actions with streaming responses
- Multi-zone deployments
Related Skills: react-expert, typescript, zod, prisma, shadcn-ui, trpc-best-practices
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
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.

