← スキル一覧に戻る

nextjs-patterns
by SPeeDoA1
⭐ 2🍴 0📅 2026年1月24日
SKILL.md
name: nextjs-patterns description: Next.js App Router patterns, Server Components, and best practices license: MIT
Next.js Patterns Skill
Modern Next.js development patterns focusing on App Router, Server Components, and production best practices.
When to Use
- Building new Next.js 13+ applications
- Migrating from Pages Router to App Router
- Implementing Server Components patterns
- Optimizing Next.js performance
App Router Structure
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
Server Components (Default)
// 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>
)
}
Client Components
// 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>
)
}
Server Actions
// 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>
)
}
Data Fetching Patterns
Parallel Data Fetching
// 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} />
}
Streaming with Suspense
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>
)
}
Caching Strategies
// 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')
Metadata
// 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],
},
}
}
Route Handlers (API)
// 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
// 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*'],
}
Error Handling
// 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>
)
}
Loading UI
// 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>
)
}
Best Practices
Component Composition
// Server Component wrapping Client Component
import { Counter } from '@/components/counter'
export default async function Page() {
const initialCount = await getCount()
return <Counter initialCount={initialCount} />
}
Colocate Related Files
app/
└── dashboard/
├── page.tsx
├── loading.tsx
├── error.tsx
├── actions.ts # Server actions
├── queries.ts # Data fetching
└── _components/ # Route-specific components
├── chart.tsx
└── stats.tsx
Environment Variables
# .env.local
DATABASE_URL="..." # Server only
NEXT_PUBLIC_API_URL="..." # Exposed to client (NEXT_PUBLIC_ prefix)
Common Pitfalls
-
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/navigationnotnext/router// App Router import { useRouter, usePathname, useSearchParams } from 'next/navigation'
Performance Checklist
- Use Server Components by default
- Implement Suspense boundaries for streaming
- Parallel fetch with
Promise.all - Use
next/imagefor images - Use
next/fontfor fonts - Configure appropriate caching
- Use route groups to organize without URL impact
- Implement proper error boundaries
スコア
総合スコア
60/100
リポジトリの品質指標に基づく評価
✓SKILL.md
SKILL.mdファイルが含まれている
+20
✓LICENSE
ライセンスが設定されている
+10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
○最近の活動
3ヶ月以内に更新がある
0/10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
○タグ
1つ以上のタグが設定されている
0/5
レビュー
💬
レビュー機能は近日公開予定です