frontendMIT License Official

Next.js App Router Architecture

Best practices for Server Components, Server Actions, route handlers, streaming SSR, and parallel route slots in Next.js.

#Next.js#React Server Components#Server Actions#SSR#Streaming
Install for:
npx domoskills add nextjs-app-router
GitHub
Security verified • Score: 100/100
Installs into: .agent/skills/nextjs-app-router
SKILL.md Prompt Instructions
Read by AI agent on demand
---
name: nextjs-app-router
description: Server Components, Server Actions, streaming SSR, caching strategies, and parallel routes in Next.js 14+.
license: MIT
version: 2.1.0
---

# Next.js App Router Architecture

## Overview
The App Router (stable in Next.js 14) defaults to React Server Components (RSC) which run exclusively on the server and ship zero JavaScript to the client. Understanding the server/client boundary is the single most important skill.

## 1. Server vs. Client Component Decision Matrix
| Need | Use |
|------|-----|
| Data fetching from database/API | Server Component |
| Event handlers (onClick, onChange) | Client Component |
| Browser APIs (localStorage, window) | Client Component |
| React Hooks (useState, useEffect) | Client Component |
| Sensitive credentials / secrets | Server Component |
| Interactive UI (modals, forms) | Client Component |

## 2. Server Component Rules
- Never add "use client" unless the component genuinely requires client capabilities.
- Push "use client" boundaries as deep as possible.
- Server Components can pass serializable props and JSX children to Client Components.
- Server Components cannot use hooks, event handlers, or browser globals.

```tsx
// app/page.tsx — Server Component (no directive needed)
import { db } from "@/lib/db";
import { ProductList } from "@/components/ProductList"; // Client Component

export default async function Page() {
  const products = await db.product.findMany(); // runs on server only
  return <ProductList products={products} />;
}
```

## 3. Data Fetching with fetch()
```tsx
// Static: cached indefinitely
const data = await fetch("https://api.example.com/data", { cache: "force-cache" });

// Dynamic: no cache, always fresh
const data = await fetch("https://api.example.com/data", { cache: "no-store" });

// Revalidate every hour (ISR equivalent)
const data = await fetch("https://api.example.com/data", { next: { revalidate: 3600 } });

// Tag-based revalidation
const data = await fetch("https://api.example.com/data", { next: { tags: ["products"] } });
```

Always fetch in parallel to avoid waterfall:
```tsx
const [user, orders] = await Promise.all([fetchUser(userId), fetchOrders(userId)]);
```

## 4. Server Actions
```tsx
"use server";

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

const schema = z.object({ title: z.string().min(1).max(200) });

export async function createPost(formData: FormData) {
  // 1. Authenticate
  const session = await auth();
  if (!session) throw new Error("Unauthorized");

  // 2. Validate
  const parsed = schema.safeParse({ title: formData.get("title") });
  if (!parsed.success) return { error: parsed.error.flatten() };

  // 3. Mutate
  await db.post.create({ data: { title: parsed.data.title, userId: session.user.id } });

  // 4. Revalidate cache
  revalidatePath("/posts");
}
```

Rules for Server Actions:
- Always validate with Zod before touching the database.
- Always authenticate the session — never trust client-passed IDs.
- Use revalidatePath or revalidateTag after mutations.

## 5. Streaming with Suspense
```tsx
import { Suspense } from "react";
import { PostsSkeleton } from "@/components/skeletons";

export default function Page() {
  return (
    <Suspense fallback={<PostsSkeleton />}>
      <SlowDataComponent />
    </Suspense>
  );
}
```

## 6. Metadata API
```tsx
// Static
export const metadata: Metadata = { title: "My Page", description: "..." };

// Dynamic
export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const product = await fetchProduct(params.id);
  return { title: product.name, openGraph: { images: [product.image] } };
}
```

## 7. Anti-Patterns
- Fetching data in Client Components on mount instead of in Server Components.
- Passing non-serializable props (class instances, functions) from Server to Client.
- Using cookies() or headers() in a layout that needs to be statically generated.
- Forgetting revalidatePath after Server Action mutations — stale UI.

Ecosystem Radar & Recommended Companions

Dynamic Capability Matrix
Standard Connectors
Antigravity (.agent)Claude Code (.claude)Cursor (.cursor)
Next.js App Router ArchitectureActive Capability