nextjs
Next.jsのルーティング、データ取得、サーバーコンポーネント、デプロイに関するパターンを理解し、適切なコードを生成するSkill。
📜 元の英語説明(参考)
Next.js patterns for routing, data fetching, server components, and deployment. Use when user mentions "next.js", "nextjs", "app router", "pages router", "server components", "server actions", "next/image", "next/link", "getServerSideProps", "getStaticProps", "middleware", or building React applications with Next.js.
🇯🇵 日本人クリエイター向け解説
Next.jsのルーティング、データ取得、サーバーコンポーネント、デプロイに関するパターンを理解し、適切なコードを生成するSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-17
- 取得日時
- 2026-05-17
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
Next.js 開発パターン
App Router と Pages Router
新規プロジェクトには App Router (Next.js 13.4+) を推奨します。Pages Router も引き続きサポートされます。
App Router を使用する場合: 新規プロジェクトを開始する場合、Server Components/ストリーミング/Server Actions、ネストされたレイアウト、またはパラレルルートが必要な場合。 Pages Router を使用する場合: 既存のコードベースを保守する場合、または App Router をサポートしていないライブラリに依存している場合。
両方のルーターは共存できます。app/ 内のファイルは App Router を使用し、pages/ 内のファイルは Pages Router を使用します。同じルートを両方で定義しないでください。
ファイルベースルーティング (App Router)
app/
layout.tsx # ルートレイアウト (必須、すべてのページをラップ、<html> と <body> が必要)
page.tsx # ホームルート (/)
loading.tsx # Suspense フォールバック (ページがストリーミング中に表示)
error.tsx # エラー境界 (Client Component である必要があります)
not-found.tsx # 404 UI (notFound() によってトリガー)
global-error.tsx # ルートレイアウトのエラー境界
template.tsx # layout と似ているが、ナビゲーション時に再マウントされる
dashboard/
layout.tsx # /dashboard/* のネストされたレイアウト
page.tsx # /dashboard
layout.tsxはナビゲーション間で永続化されます。page.tsxはルートを公開します。loading.tsxはページを自動的に<Suspense>でラップします。error.tsxは"use client"が必要で、errorとresetプロップを受け取ります。
Server Components と Client Components
App Router では、すべてのコンポーネントがデフォルトで Server Components です。
Server Components はサーバー上でのみ実行され、データベース/シークレットに直接アクセスでき、フックやイベントハンドラーは使用できず、クライアントのバンドルサイズを削減します。
Client Components は最初の行に "use client" が必要です。インタラクティブ性、フック、ブラウザ API に必要です。これらは最初のロード時に SSR され、その後ハイドレートされます。
"use client";
import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}
コンポジションパターン: Server Components を外側のラッパーとして保持し、Client Components を子として渡します。本当に必要な場合にのみ "use client" を追加してください。
データフェッチング
App Router (Server Components)
// 無期限にキャッシュされる (getStaticProps と同様)
const res = await fetch("https://api.example.com/data");
// 60秒ごとに再検証 (ISR)
const res = await fetch(url, { next: { revalidate: 60 } });
// キャッシュなし (getServerSideProps と同様)
const res = await fetch(url, { cache: "no-store" });
フェッチ以外のソースのセグメントレベル設定:
export const revalidate = 60;
export const dynamic = "force-dynamic";
Pages Router
getStaticProps-- ビルド時 (または ISR 再検証)。getServerSideProps-- リクエストごと。getStaticPaths-- 静的生成のための動的パスを定義。
Server Actions
フォームや Client Components から呼び出し可能なサーバーサイド関数です。
// 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;
await db.post.create({ data: { title } });
revalidatePath("/posts");
redirect("/posts");
}
// app/posts/new/page.tsx
import { createPost } from "@/app/actions";
export default function NewPost() {
return (
<form action={createPost}>
<input name="title" required />
<button type="submit">Create</button>
</form>
);
}
Client Components では startTransition を使用して呼び出すこともできます。入力は常にサーバーサイドで検証してください。
動的ルーティング
app/blog/[slug]/page.tsx -- /blog/hello-world
app/docs/[...catchAll]/page.tsx -- /docs/a, /docs/a/b/c
app/shop/[[...optional]]/page.tsx -- /shop, /shop/a, /shop/a/b
export default async function BlogPost({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await getPost(slug);
return <article>{post.content}</article>;
}
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map((post) => ({ slug: post.slug }));
}
ルートグループとパラレルルート
ルートグループ は、URL に影響を与えずに整理するために括弧を使用します。
app/(marketing)/about/page.tsx --> /about
app/(shop)/cart/page.tsx --> /cart
各グループは独自の layout.tsx を持つことができます。
パラレルルート は、レイアウト内で同時にレンダリングされる @ プレフィックス付きのスロットを使用します。
app/layout.tsx # Props: children, analytics, team
app/@analytics/page.tsx
app/@team/page.tsx
export default function Layout({ children, analytics, team }: {
children: React.ReactNode; analytics: React.ReactNode; team: React.ReactNode;
}) {
return <div>{children}{analytics}{team}</div>;
}
ミドルウェア
プロジェクトのルート (または src/) に middleware.ts を定義します。リクエストが完了する前に Edge Runtime で実行されます。
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const token = request.cookies.get("session")?.value;
if (!token && request.nextUrl.pathname.startsWith("/dashboard")) {
return NextResponse.redirect(new URL("/login", request.url));
}
const country = request.geo?.country;
if (country === "DE") {
return NextResponse.rewrite(new URL("/de" + request.nextUrl.pathname, request.url));
}
const response = NextResponse.next();
response.headers.set("x-request-id", crypto.randomUUID());
return response;
}
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};
ミドルウェアは軽量に保ってください。重い計算や大きな依存関係は避けてください。
API ルート
App Router (Route Handlers)
// app/api/posts/route.ts
import { NextResponse } from "next/server";
export async function GET() {
const posts = await db.post.findMany();
return NextResponse.json(posts);
}
export async function POST(request: Request) {
const 📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
Next.js Development Patterns
App Router vs Pages Router
The App Router (Next.js 13.4+) is recommended for new projects. The Pages Router remains supported.
Use App Router when: starting a new project, you need Server Components/streaming/Server Actions, nested layouts, or parallel routes. Use Pages Router when: maintaining an existing codebase, or depending on libraries without App Router support.
Both routers can coexist. Files in app/ use App Router; files in pages/ use Pages Router. Do not define the same route in both.
File-Based Routing (App Router)
app/
layout.tsx # Root layout (required, wraps all pages, must have <html> and <body>)
page.tsx # Home route (/)
loading.tsx # Suspense fallback (shown while page streams)
error.tsx # Error boundary (must be a Client Component)
not-found.tsx # 404 UI (triggered by notFound())
global-error.tsx # Error boundary for the root layout
template.tsx # Like layout but re-mounts on navigation
dashboard/
layout.tsx # Nested layout for /dashboard/*
page.tsx # /dashboard
layout.tsxpersists across navigations.page.tsxmakes a route publicly accessible.loading.tsxauto-wraps the page in<Suspense>.error.tsxrequires"use client"and receiveserrorandresetprops.
Server Components vs Client Components
All components are Server Components by default in the App Router.
Server Components run only on the server, can access databases/secrets directly, cannot use hooks or event handlers, and reduce client bundle size.
Client Components require "use client" as the first line. Needed for interactivity, hooks, browser APIs. They still SSR on initial load, then hydrate.
"use client";
import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}
Composition pattern: Keep Server Components as outer wrappers, pass Client Components as children. Only add "use client" when genuinely needed.
Data Fetching
App Router (Server Components)
// Cached indefinitely (like getStaticProps)
const res = await fetch("https://api.example.com/data");
// Revalidate every 60s (ISR)
const res = await fetch(url, { next: { revalidate: 60 } });
// No caching (like getServerSideProps)
const res = await fetch(url, { cache: "no-store" });
Segment-level config for non-fetch sources:
export const revalidate = 60;
export const dynamic = "force-dynamic";
Pages Router
getStaticProps-- build time (or ISR revalidation).getServerSideProps-- every request.getStaticPaths-- defines dynamic paths for static generation.
Server Actions
Server-side functions callable from forms and Client Components.
// 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;
await db.post.create({ data: { title } });
revalidatePath("/posts");
redirect("/posts");
}
// app/posts/new/page.tsx
import { createPost } from "@/app/actions";
export default function NewPost() {
return (
<form action={createPost}>
<input name="title" required />
<button type="submit">Create</button>
</form>
);
}
Can also be called with startTransition in Client Components. Always validate input server-side.
Dynamic Routes
app/blog/[slug]/page.tsx -- /blog/hello-world
app/docs/[...catchAll]/page.tsx -- /docs/a, /docs/a/b/c
app/shop/[[...optional]]/page.tsx -- /shop, /shop/a, /shop/a/b
export default async function BlogPost({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await getPost(slug);
return <article>{post.content}</article>;
}
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map((post) => ({ slug: post.slug }));
}
Route Groups and Parallel Routes
Route groups use parentheses to organize without affecting URLs:
app/(marketing)/about/page.tsx --> /about
app/(shop)/cart/page.tsx --> /cart
Each group can have its own layout.tsx.
Parallel routes use @-prefixed slots rendered simultaneously in a layout:
app/layout.tsx # Props: children, analytics, team
app/@analytics/page.tsx
app/@team/page.tsx
export default function Layout({ children, analytics, team }: {
children: React.ReactNode; analytics: React.ReactNode; team: React.ReactNode;
}) {
return <div>{children}{analytics}{team}</div>;
}
Middleware
Define middleware.ts at the project root (or src/). Runs before requests complete, on the Edge Runtime.
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const token = request.cookies.get("session")?.value;
if (!token && request.nextUrl.pathname.startsWith("/dashboard")) {
return NextResponse.redirect(new URL("/login", request.url));
}
const country = request.geo?.country;
if (country === "DE") {
return NextResponse.rewrite(new URL("/de" + request.nextUrl.pathname, request.url));
}
const response = NextResponse.next();
response.headers.set("x-request-id", crypto.randomUUID());
return response;
}
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};
Keep middleware lightweight. Avoid heavy computation or large dependencies.
API Routes
App Router (Route Handlers)
// app/api/posts/route.ts
import { NextResponse } from "next/server";
export async function GET() {
const posts = await db.post.findMany();
return NextResponse.json(posts);
}
export async function POST(request: Request) {
const body = await request.json();
const post = await db.post.create({ data: body });
return NextResponse.json(post, { status: 201 });
}
Supports GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS. GET-only route files are cached by default.
Pages Router
// pages/api/posts.ts
export default function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "POST") { /* handle */ }
res.status(200).json({ posts: [] });
}
Image Optimization
import Image from "next/image";
import heroImage from "@/public/hero.jpg";
<Image src={heroImage} alt="Hero" priority />
<Image src="https://cdn.example.com/photo.jpg" alt="Photo"
width={800} height={600} sizes="(max-width: 768px) 100vw, 800px" />
priorityon above-the-fold images (disables lazy loading).sizeshelps the browser pick the correct srcset image.- Configure
remotePatternsinnext.config.jsfor external domains. fillprop with a positioned parent for responsive container-filling images.
Metadata and SEO
// Static
export const metadata = {
title: "My App",
description: "A description",
openGraph: { title: "My App", images: ["/og.png"] },
};
// Dynamic
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await getPost(slug);
return { title: post.title, description: post.summary };
}
Sitemap and robots:
// app/sitemap.ts
export default async function sitemap() {
const posts = await getAllPosts();
return [
{ url: "https://example.com", lastModified: new Date() },
...posts.map((p) => ({ url: `https://example.com/blog/${p.slug}`, lastModified: p.updatedAt })),
];
}
// app/robots.ts
export default function robots() {
return {
rules: { userAgent: "*", allow: "/", disallow: "/private/" },
sitemap: "https://example.com/sitemap.xml",
};
}
Environment Variables
NEXT_PUBLIC_prefix: inlined into the client bundle at build time, visible to the browser.- All other variables: server-only (Server Components, Route Handlers, Middleware).
- Use
.env.localfor local overrides. Commit.env.examplewith placeholders. - Consider
@t3-oss/env-nextjsorzodfor startup validation.
DATABASE_URL=postgres://localhost:5432/mydb # server only
NEXT_PUBLIC_API_URL=https://api.example.com # available in browser
Caching
Four layers:
- Request Memoization -- Duplicate
fetchcalls with same URL/options are deduplicated within a single render. - Data Cache --
fetchresponses persisted. Invalidate withrevalidatePath,revalidateTag, or time-based revalidation. - Full Route Cache -- Static routes cached as HTML + RSC payload at build time. Invalidated when Data Cache invalidates.
- Router Cache -- RSC payloads cached in the browser per session. Configurable staleness.
Invalidation:
import { revalidatePath, revalidateTag } from "next/cache";
const res = await fetch(url, { next: { tags: ["posts"] } });
revalidateTag("posts"); // tag-based
revalidatePath("/blog"); // path-based
export const dynamic = "force-dynamic"; // opt out entirely
Tune Router Cache in next.config.js:
module.exports = { experimental: { staleTimes: { dynamic: 0, static: 180 } } };
Deployment
Vercel: Push to a connected Git repo. Zero config. Supports Edge Functions, ISR, image optimization.
Self-hosted:
next build && next start -p 3000
Docker:
FROM node:20-alpine AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM base AS build
RUN npm ci
COPY . .
RUN npm run build
FROM base AS runner
COPY --from=build /app/.next/standalone ./
COPY --from=build /app/.next/static ./.next/static
COPY --from=build /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]
Requires output: "standalone" in next.config.js.
Static export: Set output: "export" in next.config.js. Outputs static HTML/CSS/JS to out/. No ISR, middleware, non-GET route handlers, or image optimization.
Common Patterns
Auth Middleware
const publicPaths = ["/login", "/register", "/api/auth"];
export function middleware(request: NextRequest) {
const isPublic = publicPaths.some((p) => request.nextUrl.pathname.startsWith(p));
if (isPublic) return NextResponse.next();
const session = request.cookies.get("session");
if (!session) return NextResponse.redirect(new URL("/login", request.url));
return NextResponse.next();
}
Internationalization (i18n)
const locales = ["en", "fr", "de"];
const defaultLocale = "en";
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const hasLocale = locales.some((l) => pathname.startsWith(`/${l}/`) || pathname === `/${l}`);
if (hasLocale) return NextResponse.next();
const preferred = request.headers.get("accept-language")?.split(",")[0].split("-")[0];
const locale = locales.includes(preferred ?? "") ? preferred : defaultLocale;
return NextResponse.rewrite(new URL(`/${locale}${pathname}`, request.url));
}
Dynamic OG Images
// app/api/og/route.tsx
import { ImageResponse } from "next/og";
export async function GET(request: Request) {
const title = new URL(request.url).searchParams.get("title") ?? "Default";
return new ImageResponse(
(<div style={{ display: "flex", fontSize: 60, background: "white",
width: "100%", height: "100%", alignItems: "center", justifyContent: "center" }}>
{title}
</div>),
{ width: 1200, height: 630 }
);
}
Performance Tips
- Streaming with Suspense: Wrap slow components in
<Suspense>with a fallback. The shell renders immediately. - Partial Prerendering (experimental): Static shell with dynamic holes that stream in.
- Parallel data fetching: Use
Promise.allfor independent fetches instead of sequential awaits. - Minimize
"use client": Push interactivity to leaf components to reduce client JS. - Lazy-load heavy components:
import dynamic from "next/dynamic"; const Chart = dynamic(() => import("./chart"), { ssr: false }); - Optimize fonts: Use
next/fontfor self-hosted fonts with zero layout shift. - Analyze bundles:
ANALYZE=true next buildwith@next/bundle-analyzer. - Prefer Server Components for data display -- zero client bundle cost.
- Use
React.cachefor request-level deduplication of non-fetch functions.