← スキル一覧に戻る

nextjs
by mgd34msu
Plug in, Receive good vibes.
⭐ 2🍴 0📅 2026年1月25日
SKILL.md
name: nextjs description: Builds full-stack React applications with Next.js App Router, Server Components, Server Actions, and edge deployment. Use when creating Next.js projects, implementing routing, data fetching, caching, authentication, or deploying to Vercel.
Next.js
Full-stack React framework with App Router, Server Components, Server Actions, and optimized deployment patterns.
Quick Start
Create new project:
npx create-next-app@latest my-app --typescript --tailwind --eslint --app --src-dir
cd my-app && npm run dev
Essential file structure:
src/
app/
layout.tsx # Root layout (required)
page.tsx # Home page
globals.css # Global styles
api/ # Route handlers
components/ # React components
lib/ # Utilities
App Router Fundamentals
File Conventions
| File | Purpose |
|---|---|
page.tsx | Route UI |
layout.tsx | Shared UI wrapper |
loading.tsx | Loading UI (Suspense) |
error.tsx | Error boundary |
not-found.tsx | 404 UI |
route.ts | API endpoint |
Routing Patterns
app/
page.tsx # /
blog/
page.tsx # /blog
[slug]/
page.tsx # /blog/:slug
(marketing)/ # Route group (no URL segment)
about/page.tsx # /about
@modal/ # Parallel route (slot)
(.)photo/[id]/page.tsx # Intercepting route
Dynamic segments:
// app/blog/[slug]/page.tsx
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
return <Article slug={slug} />
}
Catch-all segments:
// app/docs/[...slug]/page.tsx - matches /docs/a, /docs/a/b, etc.
// app/docs/[[...slug]]/page.tsx - also matches /docs
Layouts
// app/layout.tsx - Root layout (required)
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
Nested layout:
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="flex">
<Sidebar />
<main className="flex-1">{children}</main>
</div>
)
}
Server Components
Default behavior - all components are Server Components unless marked with 'use client'.
// Server Component (default)
async function Posts() {
const posts = await db.posts.findMany() // Direct DB access
return (
<ul>
{posts.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
)
}
When to use Client Components:
- Event handlers (onClick, onChange)
- State and lifecycle (useState, useEffect)
- Browser APIs
- Custom hooks with state
'use client'
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}
Data Fetching
Server Components (Recommended)
// Direct fetch in Server Component
async function BlogPosts() {
const res = await fetch('https://api.example.com/posts')
const posts = await res.json()
return <PostList posts={posts} />
}
// With ORM
async function Users() {
const users = await prisma.user.findMany()
return <UserList users={users} />
}
Parallel Data Fetching
export default async function Page() {
// Start both requests simultaneously
const postsPromise = getPosts()
const usersPromise = getUsers()
// Await both
const [posts, users] = await Promise.all([postsPromise, usersPromise])
return (
<>
<PostList posts={posts} />
<UserList users={users} />
</>
)
}
Streaming with Suspense
import { Suspense } from 'react'
export default function Page() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<Skeleton />}>
<SlowComponent />
</Suspense>
</div>
)
}
Server Actions
Basic Form
// app/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
export async function createPost(formData: FormData) {
const title = formData.get('title') as string
const content = formData.get('content') as string
await db.posts.create({ data: { title, content } })
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 Post</button>
</form>
)
}
With Validation
'use server'
import { z } from 'zod'
const PostSchema = z.object({
title: z.string().min(1).max(100),
content: z.string().min(1),
})
export async function createPost(formData: FormData) {
const validated = PostSchema.safeParse({
title: formData.get('title'),
content: formData.get('content'),
})
if (!validated.success) {
return { error: validated.error.flatten().fieldErrors }
}
await db.posts.create({ data: validated.data })
revalidatePath('/posts')
redirect('/posts')
}
With useActionState
'use client'
import { useActionState } from 'react'
import { createPost } from '@/app/actions'
export function CreatePostForm() {
const [state, action, pending] = useActionState(createPost, null)
return (
<form action={action}>
<input name="title" />
{state?.error?.title && <p>{state.error.title}</p>}
<button disabled={pending}>
{pending ? 'Creating...' : 'Create'}
</button>
</form>
)
}
Caching
Data Cache
// Cached by default (static)
const data = await fetch('https://api.example.com/data')
// Opt out of caching
const data = await fetch('https://api.example.com/data', {
cache: 'no-store'
})
// Time-based revalidation
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 } // 1 hour
})
// Tag-based revalidation
const data = await fetch('https://api.example.com/data', {
next: { tags: ['posts'] }
})
Revalidation
'use server'
import { revalidatePath, revalidateTag } from 'next/cache'
export async function updatePost() {
// Revalidate specific path
revalidatePath('/posts')
// Revalidate by tag
revalidateTag('posts')
// Revalidate layout
revalidatePath('/posts', 'layout')
}
Route Segment Config
// Force dynamic rendering
export const dynamic = 'force-dynamic'
// Revalidate every 60 seconds
export const revalidate = 60
// Generate static params
export async function generateStaticParams() {
const posts = await getPosts()
return posts.map((post) => ({ slug: post.slug }))
}
API Routes (Route Handlers)
// app/api/posts/route.ts
import { NextResponse } from 'next/server'
export async function GET() {
const posts = await db.posts.findMany()
return NextResponse.json(posts)
}
export async function POST(request: Request) {
const body = await request.json()
const post = await db.posts.create({ data: body })
return NextResponse.json(post, { status: 201 })
}
Dynamic route handler:
// app/api/posts/[id]/route.ts
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
const post = await db.posts.findUnique({ where: { id } })
if (!post) {
return NextResponse.json({ error: 'Not found' }, { status: 404 })
}
return NextResponse.json(post)
}
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*'],
}
Metadata & SEO
// Static metadata
export const metadata = {
title: 'My App',
description: 'App description',
openGraph: {
title: 'My App',
description: 'App description',
images: ['/og.png'],
},
}
// Dynamic metadata
export async function generateMetadata({ params }) {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
}
}
Image Optimization
import Image from 'next/image'
export function Avatar() {
return (
<Image
src="/avatar.png"
alt="Avatar"
width={64}
height={64}
priority // Above the fold
/>
)
}
// Remote images (configure in next.config.js)
<Image
src="https://example.com/image.jpg"
alt="Remote image"
width={800}
height={600}
/>
Environment Variables
# .env.local (git ignored, local dev)
DATABASE_URL=postgresql://...
SECRET_KEY=abc123
# Public (exposed to browser)
NEXT_PUBLIC_API_URL=https://api.example.com
// Server only
const dbUrl = process.env.DATABASE_URL
// Client accessible
const apiUrl = process.env.NEXT_PUBLIC_API_URL
Common Patterns
Authentication Check
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
export default async function ProtectedPage() {
const cookieStore = await cookies()
const session = cookieStore.get('session')
if (!session) {
redirect('/login')
}
return <Dashboard />
}
Error Handling
// app/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 States
// app/dashboard/loading.tsx
export default function Loading() {
return <DashboardSkeleton />
}
Best Practices
- Default to Server Components - Only use Client Components when needed
- Colocate data fetching - Fetch data in the component that needs it
- Use Server Actions for mutations - Not API routes
- Implement proper loading states - Use Suspense and loading.tsx
- Configure caching appropriately - Don't over-cache dynamic content
- Use
generateStaticParams- For static generation of dynamic routes
Common Mistakes
| Mistake | Fix |
|---|---|
Using 'use client' everywhere | Only use for interactivity |
| Fetching in layout for child data | Fetch in the page/component that needs it |
Not awaiting params/searchParams | These are now Promises in Next.js 15 |
| Using API routes for mutations | Use Server Actions instead |
| Forgetting to revalidate cache | Call revalidatePath/revalidateTag |
Reference Files
- references/app-router.md - Advanced routing patterns
- references/caching.md - Caching strategies deep dive
- references/server-actions.md - Server Actions patterns
- references/deployment.md - Vercel and self-hosting
Templates
- templates/page.tsx - Page component template
- templates/layout.tsx - Layout component template
- templates/server-action.ts - Server Action template
スコア
総合スコア
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
レビュー
💬
レビュー機能は近日公開予定です