# Next.js

> ContextOS skill for Next.js App Router, Server Components, Server Actions, performance optimization, and Vercel best practices.

- Skill: `kok-o/next-js-2` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add kok-o/next-js-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/kok-o/next-js-2/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: kok-o (https://skillmd.com/u/kok-o)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/kok-o/next-js-2

---


# Next.js App Router Best Practices

## Overview

Enforces high-performance architectural patterns for Next.js App Router based on Vercel Engineering guidelines: React Server Components (RSC), zero-waterfall async pipelines, request deduplication via `React.cache()`, bundle optimization, and secure Server Actions.

## When to Use

Activate whenever building, refactoring, or reviewing Next.js pages, layouts, Route Handlers (`app/api`), Server Actions, or components in the `app/` directory.

## Negative Constraints (What NOT to Do)

1. **NEVER use barrel imports for UI libraries**: Avoid `import { Button, Dialog } from '@/components'`. Import directly from the exact file (`import { Button } from '@/components/ui/button'`) to prevent bundler tree-shaking failures and trace bloat.
2. **NEVER trust client-provided data or session state in Server Actions**: Always authenticate session and authorize tenant ownership inside the Server Action handler itself before mutating data.
3. **NEVER introduce sequential `await` waterfalls for independent data**: Always use `Promise.all()` or parallel streaming `<Suspense>` boundaries.
4. **NEVER pass large unneeded serialized data from Server to Client Components**: Only pass the specific primitive fields required by the client component (`server-dedup-props`).
5. **NEVER use `useEffect` for data fetching**: Fetch directly in Server Components or use TanStack Query / SWR for client-side queries.
6. **NEVER import server-only modules in client components**: Use the `server-only` package in data access layers to catch accidental client imports at build time.

## Rules & Patterns

### 1. Eliminating Async Waterfalls (Critical)

- **Parallel Fetching**: Fetch independent data concurrently at the top of the route or component.
- **Granular Streaming**: Wrap slow, non-critical subtrees in `<Suspense fallback={<Skeleton />}>` so critical above-the-fold content streams immediately.
- **Defer Awaits**: Check cheap synchronous conditions before awaiting remote resources.

### 2. Request Deduplication & Caching (`server-cache-react`)

- Use `React.cache()` to deduplicate identical database or service calls across multiple components rendered in the same server request lifecycle.

```tsx
import { cache } from 'react';
import { db } from '@/lib/db';

export const getCurrentUser = cache(async (userId: string) => {
  return await db.user.findUnique({
    where: { id: userId },
    select: { id: true, name: true, role: true, email: true }
  });
});
```

### 3. Secure Server Actions (`server-auth-actions`)

- Treat every Server Action as a public HTTP endpoint. Always validate session, authorization, and input schema with Zod.

```tsx
'use server';

import { z } from 'zod';
import { auth } from '@/lib/auth';
import { db } from '@/lib/db';
import { revalidatePath } from 'next/cache';

const UpdateProfileSchema = z.object({
  name: z.string().min(2).max(50),
});

export async function updateProfile(formData: FormData) {
  const session = await auth();
  if (!session?.userId) throw new Error('Unauthorized');

  const result = UpdateProfileSchema.safeParse({ name: formData.get('name') });
  if (!result.success) return { error: 'Invalid input', issues: result.error.flatten() };

  await db.user.update({
    where: { id: session.userId },
    data: { name: result.data.name },
  });

  revalidatePath('/settings');
  return { success: true };
}
```

### 4. Bundle Optimization & Dynamic Imports (`bundle-dynamic-imports`)

- Heavy interactive client components (charts, rich-text editors, video players) must be dynamically loaded with `next/dynamic`.

```tsx
import dynamic from 'next/dynamic';

const AnalyticsChart = dynamic(
  () => import('@/components/analytics/chart').then(mod => mod.AnalyticsChart),
  {
    loading: () => <div className="h-64 animate-pulse bg-muted rounded-lg" />,
    ssr: false,
  }
);
```

### 5. Next.js 15+ Async Request APIs (`async-params`)

In Next.js 15+, `params`, `searchParams`, `cookies()`, and `headers()` are asynchronous and must be awaited:

```tsx
// [GOOD] Next.js 15+ Page Component
interface PageProps {
  params: Promise<{ id: string }>;
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}

export default async function UserPage({ params, searchParams }: PageProps) {
  const { id } = await params;
  const { tab } = await searchParams;
  const user = await getUser(id);

  return <UserProfile user={user} activeTab={tab as string} />;
}
```

### 6. Non-Blocking Background Tasks with `after()`

To execute logging, analytics, or cache priming without delaying the user's HTTP response:

```typescript
import { after } from 'next/server';

export async function POST(request: Request) {
  const data = await request.json();
  const result = await processOrder(data);

  // Executes asynchronously AFTER the response stream has completed
  after(async () => {
    await sendSlackNotification(result);
    await indexOrderInSearch(result.id);
  });

  return Response.json({ success: true, orderId: result.id });
}
```

---

## Code Examples

See `EXAMPLES.md` for detailed code examples and component templates.

## Validation Checklist

- [ ] All database queries in RSC layers use `React.cache()` if called across multiple components.
- [ ] No barrel imports (`from '@/components'`); all imports point to exact component modules.
- [ ] Server Actions have explicit auth checks and Zod input validation.
- [ ] Heavy client widgets (charts, editors) use `next/dynamic`.
- [ ] Images use `next/image` with explicit `sizes` and `priority` on LCP elements.

## Common Mistakes

- Using `'use client'` at page level instead of leaf components.
- Relying on client-side authentication checks for Server Actions without server-side validation.
- Chaining sequential awaits for independent data models.

## Integration Notes

- Pairs with `react` and `ui-ux-pro` for component design and state management.
- Pairs with `security` for session authorization and input sanitization.

