com um clique
nextjs-patterns
Next.js App Router patterns, Server Components, and best practices
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Menu
Next.js App Router patterns, Server Components, and best practices
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Baseado na classificação ocupacional SOC
Web accessibility audit workflow for WCAG compliance
RESTful and GraphQL API design patterns and best practices
Structured code review workflow for quality and consistency
Safe database migration workflow with zero-downtime strategies
Git release workflow with semantic versioning and changelogs
Comprehensive performance audit workflow for web applications
| name | nextjs-patterns |
| description | Next.js App Router patterns, Server Components, and best practices |
| license | MIT |
Modern Next.js development patterns focusing on App Router, Server Components, and production best practices.
app/
├── layout.tsx # Root layout (required)
├── page.tsx # Home page
├── loading.tsx # Loading UI
├── error.tsx # Error boundary
├── not-found.tsx # 404 page
├── global-error.tsx # Global error boundary
├── (auth)/ # Route group (no URL impact)
│ ├── login/page.tsx
│ └── register/page.tsx
├── dashboard/
│ ├── layout.tsx # Nested layout
│ ├── page.tsx
│ └── [id]/ # Dynamic route
│ └── page.tsx
└── api/
└── route.ts # API route handler
// app/users/page.tsx - Server Component by default
import { db } from '@/lib/db'
// Direct database access - no API needed
export default async function UsersPage() {
const users = await db.user.findMany()
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
// components/counter.tsx
'use client' // Required directive
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
)
}
// app/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
import { z } from 'zod'
const schema = z.object({
title: z.string().min(1),
content: z.string().min(10),
})
export async function createPost(formData: FormData) {
const validated = schema.parse({
title: formData.get('title'),
content: formData.get('content'),
})
await db.post.create({ data: validated })
revalidatePath('/posts')
redirect('/posts')
}
// app/posts/new/page.tsx
import { createPost } from '@/app/actions'
export default function NewPostPage() {
return (
<form action={createPost}>
<input name="title" required />
<textarea name="content" required />
<button type="submit">Create</button>
</form>
)
}
// Fetch in parallel - don't await sequentially
export default async function Page() {
const [users, posts] = await Promise.all([
getUsers(),
getPosts(),
])
return <Dashboard users={users} posts={posts} />
}
import { Suspense } from 'react'
import { UserList, UserListSkeleton } from '@/components/user-list'
import { PostList, PostListSkeleton } from '@/components/post-list'
export default function Page() {
return (
<div className="grid grid-cols-2 gap-4">
<Suspense fallback={<UserListSkeleton />}>
<UserList />
</Suspense>
<Suspense fallback={<PostListSkeleton />}>
<PostList />
</Suspense>
</div>
)
}
// Default: cached indefinitely (static)
fetch('https://api.example.com/data')
// Revalidate every 60 seconds
fetch('https://api.example.com/data', {
next: { revalidate: 60 }
})
// No cache (dynamic)
fetch('https://api.example.com/data', {
cache: 'no-store'
})
// Tag-based revalidation
fetch('https://api.example.com/posts', {
next: { tags: ['posts'] }
})
// In server action:
import { revalidateTag } from 'next/cache'
revalidateTag('posts')
// Static metadata
export const metadata = {
title: 'My App',
description: 'App description',
}
// Dynamic metadata
export async function generateMetadata({ params }) {
const post = await getPost(params.id)
return {
title: post.title,
openGraph: {
images: [post.image],
},
}
}
// app/api/users/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 users = await db.user.findMany({
where: query ? { name: { contains: query } } : undefined,
})
return NextResponse.json(users)
}
export async function POST(request: NextRequest) {
const body = await request.json()
const user = await db.user.create({ data: body })
return NextResponse.json(user, { status: 201 })
}
// middleware.ts (root level)
import { NextResponse } from 'next/server'
import type { NextRequest } 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 headers
const response = NextResponse.next()
response.headers.set('x-custom-header', 'value')
return response
}
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*'],
}
// 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>
<button onClick={() => reset()}>Try again</button>
</div>
)
}
// app/dashboard/loading.tsx
export default function Loading() {
return (
<div className="animate-pulse">
<div className="h-8 bg-gray-200 rounded w-1/4 mb-4" />
<div className="h-4 bg-gray-200 rounded w-full mb-2" />
<div className="h-4 bg-gray-200 rounded w-3/4" />
</div>
)
}
// Server Component wrapping Client Component
import { Counter } from '@/components/counter'
export default async function Page() {
const initialCount = await getCount()
return <Counter initialCount={initialCount} />
}
app/
└── dashboard/
├── page.tsx
├── loading.tsx
├── error.tsx
├── actions.ts # Server actions
├── queries.ts # Data fetching
└── _components/ # Route-specific components
├── chart.tsx
└── stats.tsx
# .env.local
DATABASE_URL="..." # Server only
NEXT_PUBLIC_API_URL="..." # Exposed to client (NEXT_PUBLIC_ prefix)
Don't import server-only code in client components
// Use 'server-only' package to prevent mistakes
import 'server-only'
import { db } from './db'
Don't pass non-serializable props to client components
// Wrong: passing function from server to client
<ClientComponent onClick={handleClick} />
// Right: define handler in client component
Use next/navigation not next/router
// App Router
import { useRouter, usePathname, useSearchParams } from 'next/navigation'
Promise.allnext/image for imagesnext/font for fonts