本文へ移動
cccskills
無料GitHub で公開

nextjs-data-fetching

Data fetching, caching, and revalidation patterns in Next.js App Router. Covers native fetch caching, ISR/SSG/SSR per-route choice, generateStaticParams, unstable_cache, revalidatePath/revalidateTag, Route Handlers, dynamic vs static rendering. Use this skill to: - Pick the right rendering mode (SSG / ISR / SSR / streaming) per route. - Use native fetch caching options correctly. - Build Route Handlers that match REST conventions. - Invalidate cached data after mutations. - Use generateStaticParams for dynamic SSG. Do NOT use this skill for: - RSC vs Client boundaries (see server-component-patterns). - Routing primitives (see nextjs-routing). - General conventions (see nextjs-conventions).

インストール方法を見る

含まれるファイル(1)

  • SKILL.md9.1 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Next.js Data Fetching Patterns

Next.js extends fetch() with caching/revalidation primitives, and offers unstable_cache for non-fetch async work. Plus revalidatePath / revalidateTag to invalidate after mutations. Get this layer right and pages become fast and consistent.

Rendering modes

Per-route, the decision tree:

NeedModeSetup
Data unchanged after build (marketing, docs)SSG (Static)Default behavior; no special options.
Data changes occasionally (hourly/daily)ISR (revalidate)fetch(url, { next: { revalidate: 3600 } })
Data per-request (dashboards, account pages)SSR (Dynamic)fetch(url, { cache: 'no-store' }) or export const dynamic = 'force-dynamic'
Data dynamic but tag-invalidatableTagged ISRfetch(url, { next: { tags: ['users'] } }) + revalidateTag('users')
Streaming / progressiveRSC + Suspensewrap async children in <Suspense fallback={...}>

By default, Next.js prerenders pages at build time (SSG). Adding cache: 'no-store' or reading dynamic APIs (cookies(), headers(), searchParams) opts the page into SSR.

Native fetch extensions

// SSG: cached forever (default)
const res = await fetch('https://api.example.com/data');

// Static + revalidate every 60s (ISR)
const res = await fetch('https://api.example.com/data', { next: { revalidate: 60 } });

// Always fresh (SSR)
const res = await fetch('https://api.example.com/data', { cache: 'no-store' });

// Tag-based: invalidate manually via revalidateTag
const res = await fetch('https://api.example.com/data', { next: { tags: ['users'] } });

fetch() deduplication: identical fetches within a single render are deduped automatically — call freely from multiple components without worry.

unstable_cache for non-fetch async work

Wrap any async function with unstable_cache:

import { unstable_cache } from 'next/cache';
import { db } from '@/lib/db';

export const getUsers = unstable_cache(
  async () => db.users.findMany(),
  ['users'],                        // cache key parts (must be a stable array)
  { revalidate: 60, tags: ['users'] }
);

Use for ORM calls, computed data, anything you'd otherwise duplicate logic to make cacheable.

The cache key parts must be stable strings. To cache PER ARGUMENT, include arguments:

export const getUserById = (id: string) =>
  unstable_cache(
    async () => db.users.findUnique({ where: { id } }),
    ['user', id],
    { revalidate: 60, tags: [`user:${id}`] }
  )();

Despite the name, unstable_cache is widely used; it's "unstable" by Next's API stability label, not "unreliable."

generateStaticParams

For dynamic routes that should be statically generated at build:

// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await db.post.findMany({ select: { slug: true } });
  return posts.map((p) => ({ slug: p.slug }));
}

// dynamicParams (default true) — allow on-demand generation for slugs not pre-built
export const dynamicParams = true;

export default async function PostPage({ params }: { params: { slug: string } }) {
  const post = await db.post.findUnique({ where: { slug: params.slug } });
  if (!post) notFound();
  return <article>{post.content}</article>;
}

Pair with revalidate for ISR — pages are built initially, then refreshed per the schedule.

Route segment config

Per-route configuration via exported constants:

// app/some/page.tsx
export const dynamic = 'auto' | 'force-dynamic' | 'error' | 'force-static';
export const dynamicParams = true | false;
export const revalidate = false | 0 | number;
export const fetchCache = 'auto' | 'default-cache' | 'only-cache' | 'force-cache' | 'force-no-store' | 'default-no-store' | 'only-no-store';
export const runtime = 'nodejs' | 'edge';
export const preferredRegion = 'auto' | 'global' | 'home' | string | string[];
export const maxDuration = number;

Common patterns:

  • Marketing page → defaults (SSG).
  • User dashboard → export const dynamic = 'force-dynamic'.
  • API route that hits DB → export const runtime = 'nodejs' (DB clients usually don't run on edge).

Route Handlers (API endpoints)

app/api/<resource>/route.ts:

import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { db } from '@/lib/db';
import { auth } from '@/lib/auth';

// GET /api/users
export async function GET() {
  const users = await db.users.findMany();
  return NextResponse.json(users);
}

// POST /api/users
const CreateUserSchema = z.object({ email: z.string().email(), name: z.string().min(1) });

export async function POST(req: NextRequest) {
  const session = await auth();
  if (!session?.user) return NextResponse.json({ error: 'unauthorized' }, { status: 401 });

  const body = await req.json();
  const parsed = CreateUserSchema.safeParse(body);
  if (!parsed.success) return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });

  const user = await db.users.create({ data: parsed.data });
  return NextResponse.json(user, { status: 201 });
}

Dynamic Route Handlers

// app/api/users/[id]/route.ts
export async function GET(_req: NextRequest, { params }: { params: { id: string } }) {
  const user = await db.users.findUnique({ where: { id: params.id } });
  if (!user) return NextResponse.json({ error: 'not found' }, { status: 404 });
  return NextResponse.json(user);
}

export async function DELETE(_req: NextRequest, { params }: { params: { id: string } }) {
  await db.users.delete({ where: { id: params.id } });
  return new Response(null, { status: 204 });
}

In Next.js 15+, params is a Promise — await params.

Route Handler caching

Route Handlers are dynamic by default in modern Next.js. To cache GETs:

export const dynamic = 'force-static'; // cache the GET response
export const revalidate = 60;

For most API endpoints, leave dynamic.

Revalidation after mutations

// in a Server Action
import { revalidatePath, revalidateTag } from 'next/cache';

export async function deleteUser(id: string) {
  'use server';
  await db.users.delete({ where: { id } });
  revalidatePath('/users');                 // path-based — invalidates that route's cache
  revalidateTag('users');                   // tag-based — invalidates anything fetched with this tag
}

revalidatePath is the simpler tool. revalidateTag is more granular when one resource is fetched in many places.

Streaming with Suspense

// app/dashboard/page.tsx
import { Suspense } from 'react';

export default function DashboardPage() {
  return (
    <>
      <h1>Dashboard</h1>
      <Suspense fallback={<div>Loading recent activity...</div>}>
        <RecentActivity />     {/* async RSC */}
      </Suspense>
      <Suspense fallback={<div>Loading metrics...</div>}>
        <Metrics />            {/* async RSC */}
      </Suspense>
    </>
  );
}

async function RecentActivity() {
  const items = await getActivityFromSlowAPI();
  return <ul>{items.map(...)}</ul>;
}

The shell renders immediately; each Suspense boundary streams in independently. Combine with loading.tsx for the whole-segment fallback.

Edge vs Node runtime

export const runtime = 'edge';   // or 'nodejs' (default)

Edge:

  • Lower cold-start, geographically distributed.
  • Limited Node APIs — no fs, no native modules.
  • DB clients usually don't work on edge (use HTTP-based clients like Neon serverless, Planetscale's HTTP client).

Default to nodejs unless you have a clear performance reason and your code is edge-compatible.

Anti-patterns

  • ❌ Fetching the same data multiple times across components without leveraging fetch dedup or unstable_cache.
  • ❌ Using cache: 'no-store' everywhere — defeats Next's caching, makes every request hit origin.
  • ❌ Forgetting revalidatePath after a mutation — users see stale data.
  • ❌ cookies() / headers() reads in a function intended to be cached — they make the route dynamic.
  • ❌ DB client on runtime: 'edge' without verifying it's edge-compatible.
  • ❌ generateStaticParams returning all DB rows at build for a million-row table — use ISR with a small initial set.
  • ❌ Reading searchParams in a Server Component and assuming the page can be static — searchParams force dynamic rendering.
  • ❌ Returning NextResponse.json(undefined) — pass null or omit the body.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Angular 18-21 project structure, standalone components vs NgModule, control flow (@if/@for/@switch + *ngIf/*ngFor legacy), decorators, dependency injection (inject() function), lifecycle hooks, pipes, Angular Universal SSR pointer. Use this skill to: - Detect project style (standalone vs NgModule) and apply matching patterns. - Pick correct decorators and DI approach. - Use modern control flow (@if/@for/@switch) in Angular 17+ projects. - Apply `inject()` function over constructor injection where appropriate. - Wire bootstrap correctly (bootstrapApplication for standalone, AppModule for legacy). Do NOT use this skill for: - State management (see angular-state-and-rx). - Routing (see angular-routing). - Forms (see angular-forms). - Testing (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Angular forms: Reactive Forms (preferred — typed FormGroup/FormControl since Angular 14, FormBuilder, custom + async validators, FormArray, multi-step) and Template-driven (`[(ngModel)]` + FormsModule). Validation strategies, server error mapping, accessibility. Use this skill to: - Build Reactive Forms with typed FormGroup/FormControl. - Use FormBuilder для concise syntax. - Implement custom synchronous and async validators. - Wire FormArray for dynamic field lists. - Map server errors back to form fields. - Pick Reactive vs Template-driven (prefer Reactive). Do NOT use this skill for: - General conventions (see angular-conventions). - State management beyond forms (see angular-state-and-rx). - Routing (see angular-routing). - Testing forms (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Angular Router (built-in `@angular/router`) — route configuration for standalone and NgModule projects, functional guards (Angular 14.1+), lazy loading, route resolvers, typed params via signals/observables, programmatic navigation, route data and meta. Use this skill to: - Configure routes (standalone-style or NgModule-style). - Use functional guards (canActivate as function, preferred over class-based in 17+). - Lazy-load components or feature modules. - Implement auth guards via route meta + functional guards. - Read params/queries via `inject(ActivatedRoute)` + signals or RxJS. Do NOT use this skill for: - General conventions (see angular-conventions). - State management (see angular-state-and-rx). - Forms (see angular-forms). - Testing routes (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

State management for Angular 18-21: signals (signal/computed/effect), services-as-state, NgRx Store + Effects + Selectors, NgRx Component Store, NgRx Signals (newer signal-based store). RxJS essentials — operators, async pipe, takeUntilDestroyed, signal/observable interop. Use this skill to: - Pick the right state tool (signals / services / NgRx variant / vue-query equivalent). - Use signals correctly (signal/computed/effect — when each). - Build a Pinia-style service-as-state singleton. - Set up NgRx Store + Effects + Selectors. - Use RxJS without leaking subscriptions (async pipe, takeUntilDestroyed, Subject patterns). - Bridge signals ↔ observables via toSignal / toObservable. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing state (see angular-routing). - Form state (see angular-forms). - Testing state (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Testing Angular 18-21: TestBed, component harnesses (@angular/cdk/testing), Karma+Jasmine (default historical) vs Jest (jest-preset-angular, modern), Angular Testing Library (RTL-style). HttpClient mocking via HttpTestingController. NgRx Effects testing. Cypress / Playwright e2e. Use this skill to: - Detect runner (Karma+Jasmine vs Jest) and configure correctly. - Write component tests with TestBed. - Use component harnesses for Material / custom UI components. - Mock HttpClient via provideHttpClientTesting + HttpTestingController. - Test signal-based inputs with componentRef.setInput(). - Test NgRx Effects with provideMockActions. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing patterns broadly (see angular-routing — covers testing routes briefly). - Form patterns broadly (see angular-forms).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Shared conventions for every SDLC development-phase architect agent: hard rules, code quality bar, workflow steps (superpowers invocation, spec reading, codebase exploration, verification), and the report/compact-summary contract. Architects load this skill first, then apply their stack-specific instructions on top.

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

AratKruglik のスキルをすべて見る

このスキルの問題を報告する