一键导入
nextjs-workflow
Next.js framework workflow guidelines. Activate when working with Next.js projects, next.config, app router, or Next.js-specific patterns.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Next.js framework workflow guidelines. Activate when working with Next.js projects, next.config, app router, or Next.js-specific patterns.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Language-agnostic API design patterns covering REST and GraphQL, including resource naming, HTTP methods, status codes, versioning, pagination, filtering, authentication, error handling, and schema design. Activate when working with APIs, REST endpoints, GraphQL schemas, API documentation, OpenAPI/Swagger, JWT, OAuth2, endpoint design, API versioning, rate limiting, or GraphQL resolvers.
Git workflow and commit guidelines. Trigger keywords: git, commit, push, .git, version control. MUST be activated before ANY git commit, push, or version control operation. Includes security scanning for secrets (API keys, tokens, .env files), commit message formatting with HEREDOC, logical commit grouping (docs, test, feat, fix, refactor, chore, build, deps), push behavior rules, safety rules for hooks and force pushes, and CRITICAL safeguards for destructive operations (filter-branch, gc --prune, reset --hard). Activate when user requests committing changes, pushing code, creating commits, rewriting history, or performing any git operations including analyzing uncommitted changes.
Testing workflow patterns and quality standards. Activate when working with tests, test files, test directories, code quality tools, coverage reports, or testing tasks. Includes zero-warnings policy, targeted testing during development, mocking patterns, and best practices across languages.
Ansible automation workflow guidelines. Activate when working with Ansible playbooks, ansible-playbook, inventory files (.yml, .ini), or Ansible-specific patterns.
Claude Code AI-assisted development workflow. Activate when discussing Claude Code usage, AI-assisted coding, prompting strategies, or Claude Code-specific patterns.
Guidelines for containerized projects using Docker, Dockerfile, docker-compose, container, and containerization. Covers multi-stage builds, security, signal handling, entrypoint scripts, and deployment workflows.
| name | nextjs-workflow |
| description | Next.js framework workflow guidelines. Activate when working with Next.js projects, next.config, app router, or Next.js-specific patterns. |
| location | user |
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
| Task | Tool | Command |
|---|---|---|
| Run dev | Next.js | npm run dev |
| Build | Next.js | npm run build |
| Turbopack | Next.js | next dev --turbo |
| Test | Vitest | vitest |
| E2E | Playwright | playwright test |
| Lint | ESLint + next | next lint |
App Router MUST be used for all new Next.js projects. Pages Router SHOULD only be used for legacy compatibility.
app/
├── layout.tsx # Root layout (REQUIRED)
├── page.tsx # Home page
├── loading.tsx # Loading UI
├── error.tsx # Error boundary
├── not-found.tsx # 404 page
├── globals.css # Global styles
├── (group)/ # Route groups (no URL segment)
│ └── page.tsx
├── api/ # API routes
│ └── route.ts
└── [slug]/ # Dynamic routes
└── page.tsx
Route groups (groupName) SHOULD be used to:
Server Components are the DEFAULT. Client Components MUST be explicitly marked.
Client Components MUST be marked with 'use client' directive at the top of the file.
Use Client Components for:
'use client'
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(count + 1)}>{count}</button>
}
Server Components MAY import Client Components. Client Components MUST NOT import Server Components directly but MAY accept them as props.
// ServerComponent.tsx (Server Component - default)
import { ClientWrapper } from './ClientWrapper'
import { ServerChild } from './ServerChild'
export function ServerComponent() {
return (
<ClientWrapper>
<ServerChild /> {/* Passed as children prop */}
</ClientWrapper>
)
}
| File | Purpose | Required |
|---|---|---|
layout.tsx | Shared UI for segment and children | Root only |
page.tsx | Unique UI for route | Yes for route |
loading.tsx | Loading UI with Suspense | OPTIONAL |
error.tsx | Error boundary | OPTIONAL |
not-found.tsx | 404 UI | OPTIONAL |
route.ts | API endpoint | OPTIONAL |
template.tsx | Re-rendered layout | OPTIONAL |
default.tsx | Parallel route fallback | OPTIONAL |
app/
├── blog/
│ ├── [slug]/page.tsx # /blog/:slug
│ └── [...slug]/page.tsx # /blog/* (catch-all)
├── shop/
│ └── [[...slug]]/page.tsx # /shop or /shop/* (optional catch-all)
Parallel routes SHOULD be used for complex layouts with independent navigation.
app/
├── @modal/
│ └── login/page.tsx
├── @sidebar/
│ └── page.tsx
└── layout.tsx # Receives modal and sidebar as props
app/
├── feed/
│ └── (..)photo/[id]/page.tsx # Intercepts /photo/[id]
└── photo/
└── [id]/page.tsx
PPR SHOULD be enabled for pages with static shells and dynamic content.
// next.config.ts
const nextConfig = {
experimental: {
ppr: 'incremental',
},
}
// app/page.tsx
export const experimental_ppr = true
export default function Page() {
return (
<main>
<StaticHeader /> {/* Prerendered */}
<Suspense fallback={<Skeleton />}>
<DynamicContent /> {/* Streamed */}
</Suspense>
</main>
)
}
Server Actions MUST be used for form handling and data mutations.
// app/page.tsx
export default function Page() {
async function createItem(formData: FormData) {
'use server'
const name = formData.get('name')
// Database operation
revalidatePath('/')
}
return (
<form action={createItem}>
<input name="name" />
<button type="submit">Create</button>
</form>
)
}
Actions MAY be defined in separate files for reuse.
// app/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
export async function createItem(formData: FormData) {
// Validation
// Database operation
revalidatePath('/items')
redirect('/items')
}
'use client'
import { useFormStatus } from 'react-dom'
import { createItem } from './actions'
function SubmitButton() {
const { pending } = useFormStatus()
return <button disabled={pending}>{pending ? 'Creating...' : 'Create'}</button>
}
export function CreateForm() {
return (
<form action={createItem}>
<input name="name" />
<SubmitButton />
</form>
)
}
Next.js automatically deduplicates fetch requests. The same URL SHOULD be fetched in multiple components without concern.
// Both components fetch the same data - automatically deduplicated
async function Header() {
const user = await fetch('/api/user').then(r => r.json())
return <div>{user.name}</div>
}
async function Sidebar() {
const user = await fetch('/api/user').then(r => r.json())
return <div>{user.email}</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 caching (dynamic)
fetch('https://api.example.com/data', { cache: 'no-store' })
// Revalidate on-demand with tags
fetch('https://api.example.com/data', { next: { tags: ['posts'] } })
// app/actions.ts
'use server'
import { revalidatePath, revalidateTag } from 'next/cache'
export async function updatePost() {
// Update database
revalidateTag('posts') // Revalidate by tag
revalidatePath('/blog') // Revalidate by path
}
next/image MUST be used for all images.
import Image from 'next/image'
export function Avatar() {
return (
<Image
src="/avatar.jpg"
alt="User avatar"
width={64}
height={64}
priority // For LCP images
/>
)
}
<Image
src="/hero.jpg"
alt="Hero image"
fill
sizes="(max-width: 768px) 100vw, 50vw"
style={{ objectFit: 'cover' }}
/>
Remote domains MUST be configured in next.config.ts:
// next.config.ts
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'cdn.example.com',
pathname: '/images/**',
},
],
},
}
next/font SHOULD be used for optimal font loading.
// app/layout.tsx
import { Inter, Roboto_Mono } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
display: 'swap',
})
const robotoMono = Roboto_Mono({
subsets: ['latin'],
variable: '--font-roboto-mono',
display: 'swap',
})
export default function RootLayout({ children }) {
return (
<html className={`${inter.variable} ${robotoMono.variable}`}>
<body>{children}</body>
</html>
)
}
import localFont from 'next/font/local'
const myFont = localFont({
src: './fonts/MyFont.woff2',
display: 'swap',
})
// app/page.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Page Title',
description: 'Page description',
openGraph: {
title: 'OG Title',
description: 'OG Description',
images: ['/og-image.jpg'],
},
}
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'
type Props = { params: Promise<{ slug: string }> }
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
const post = await getPost(slug)
return {
title: post.title,
description: post.excerpt,
}
}
// app/layout.tsx
export const metadata: Metadata = {
title: {
template: '%s | My Site',
default: 'My Site',
},
}
// app/about/page.tsx
export const metadata: Metadata = {
title: 'About', // Results in "About | My Site"
}
Middleware MUST be placed at middleware.ts in the project root.
// middleware.ts
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))
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*'],
}
export function middleware(request: NextRequest) {
const response = NextResponse.next()
// Add headers
response.headers.set('x-custom-header', 'value')
// Rewrite (internal redirect)
if (request.nextUrl.pathname === '/old-path') {
return NextResponse.rewrite(new URL('/new-path', request.url))
}
return response
}
| Prefix | Availability | Example |
|---|---|---|
| None | Server only | DATABASE_URL |
NEXT_PUBLIC_ | Client + Server | NEXT_PUBLIC_API_URL |
// Server Component or API Route
const dbUrl = process.env.DATABASE_URL
// Client Component (MUST have NEXT_PUBLIC_ prefix)
const apiUrl = process.env.NEXT_PUBLIC_API_URL
Environment variables SHOULD be validated at build time:
// lib/env.ts
import { z } from 'zod'
const envSchema = z.object({
DATABASE_URL: z.string().url(),
NEXT_PUBLIC_API_URL: z.string().url(),
})
export const env = envSchema.parse(process.env)
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [{ "name": "next" }],
"paths": {
"@/*": ["./*"]
}
}
}
// app/dashboard/loading.tsx
export default function Loading() {
return <div className="animate-pulse">Loading...</div>
}
// 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/not-found.tsx
import Link from 'next/link'
export default function NotFound() {
return (
<div>
<h2>Not Found</h2>
<Link href="/">Return Home</Link>
</div>
)
}
import { notFound } from 'next/navigation'
async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const item = await getItem(id)
if (!item) {
notFound()
}
return <div>{item.name}</div>
}