Back to list
erikpr1994

nextjs-patterns

by erikpr1994

0🍴 0📅 Jan 22, 2026

SKILL.md


name: nextjs-patterns description: "Next.js App Router patterns, server actions, caching, and routing. Use when working with Next.js 14+ features."

Next.js Patterns

Overview

Decision guide for Next.js App Router patterns focusing on data fetching, caching, and server/client architecture.

Data Fetching Strategy

Decision Flow

Need data?
  ├─ Server Component → fetch() directly (default)
  ├─ Client interactivity needed → Server Action
  ├─ External API from client → Route Handler
  └─ Real-time updates → Route Handler + streaming

Server Component Fetching

// app/users/page.tsx - Server component (default)
async function UsersPage() {
  // Direct async - no useEffect needed
  const users = await db.user.findMany();

  // Parallel fetching
  const [users, stats] = await Promise.all([
    db.user.findMany(),
    db.stats.get(),
  ]);

  return <UserList users={users} />;
}

Server Actions

// actions/user.ts
'use server';

import { revalidatePath } from 'next/cache';

export async function createUser(formData: FormData) {
  const data = Object.fromEntries(formData);
  await db.user.create({ data });
  revalidatePath('/users');
}

// Usage in component
<form action={createUser}>
  <input name="email" />
  <button type="submit">Create</button>
</form>

Caching Strategy

ScenarioCache StrategyImplementation
Static dataFull cacheDefault behavior
User-specificNo storecache: 'no-store'
Periodic refreshTime-basedrevalidate: 3600
On-demandTag-basedrevalidateTag('users')
// Time-based revalidation
const data = await fetch(url, { next: { revalidate: 3600 } });

// Tag-based revalidation
const data = await fetch(url, { next: { tags: ['users'] } });

// In server action
revalidateTag('users');
revalidatePath('/users');

Route Organization

app/
├── (marketing)/          # Route group (no URL segment)
│   ├── page.tsx          # /
│   └── about/page.tsx    # /about
├── (app)/
│   ├── layout.tsx        # Shared app layout
│   └── dashboard/
│       ├── page.tsx      # /dashboard
│       ├── loading.tsx   # Loading UI
│       └── error.tsx     # Error boundary
├── api/
│   └── users/
│       └── route.ts      # /api/users
└── @modal/               # Parallel route for modals
    └── (.)photo/[id]/    # Intercepting route

Loading & Error Patterns

// loading.tsx - Automatic Suspense boundary
export default function Loading() {
  return <Skeleton />;
}

// error.tsx - Must be client component
'use client';
export default function Error({ error, reset }: {
  error: Error;
  reset: () => void
}) {
  return (
    <div>
      <p>Something went wrong</p>
      <button onClick={reset}>Try again</button>
    </div>
  );
}

// not-found.tsx - Custom 404
export default function NotFound() {
  return <p>Page not found</p>;
}

Middleware Patterns

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Auth check
  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*'],
};

Anti-Patterns

Anti-PatternProblemSolution
Client-side fetch in Server ComponentDefeats SSRUse async/await directly
'use client' on layoutMakes all children clientPush client boundary down
Dynamic params without generateStaticParamsSlow buildsDefine static params
Fetch in useEffectRace conditions, waterfallsServer component or action
Ignoring cache defaultsOver-fetchingUnderstand cache behavior

Route Handlers

// app/api/users/route.ts
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const users = await db.user.findMany();
  return NextResponse.json(users);
}

export async function POST(request: Request) {
  const body = await request.json();
  const user = await db.user.create({ data: body });
  return NextResponse.json(user, { status: 201 });
}

Red Flags

  • router.push for form submissions (use server actions)
  • Fetching same data in parent and child (lift up)
  • revalidate: 0 everywhere (understand caching first)
  • Route handlers for what server actions can do
  • dynamic = 'force-dynamic' without justification

Quick Reference

// Generate metadata
export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const product = await getProduct(params.id);
  return { title: product.name };
}

// Static params for dynamic routes
export async function generateStaticParams() {
  const posts = await getPosts();
  return posts.map(post => ({ slug: post.slug }));
}

// Streaming with Suspense
<Suspense fallback={<Loading />}>
  <SlowComponent />
</Suspense>

Score

Total Score

50/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon