paste to any AI agent
assumes
- you're using the Next.js App Router (not the Pages Router). server-component vs. client-component distinctions, layouts, and route handlers all assume App Router semantics. - Node.js is installed and runnable. familiarity with React and TypeScript is assumed. - you have or can create `next.config.{js,ts}` and `package.json`. dependencies will be installed via your package manager of choice. Next.js App Router context for agents. What level to work at, what the Pages Router–trained agent gets wrong, and the stable mental model underneath version churn.
view raw
# Usage: curl -sSL https://seed.show/nextjs.app.router | bash -s <install-path>
# <install-path> is the directory where the file should land.
set -euo pipefail
# seed.show — assumption contract for nextjs.app.router
# (printed to stderr before unpack so the agent sees it in tool output)
cat >&2 <<'ASSUMPTIONS_EOF'
- you're using the Next.js App Router (not the Pages Router). server-component vs. client-component distinctions, layouts, and route handlers all assume App Router semantics.
- Node.js is installed and runnable. familiarity with React and TypeScript is assumed.
- you have or can create `next.config.{js,ts}` and `package.json`. dependencies will be installed via your package manager of choice.
Next.js App Router context for agents. What level to work at, what the Pages Router–trained agent gets wrong, and the stable mental model underneath version churn.
ASSUMPTIONS_EOF
[ -z "${1:-}" ] && {
echo "install requires a path: curl -sSL https://seed.show/nextjs.app.router | bash -s <install-path>" >&2
exit 1
}
TARGET="$1"
mkdir -p "$TARGET"
DEST="$TARGET/seed-fold.Nnd4hW.folded.md"
cat > "$DEST" <<'PORTDOWN_03A71DE2'
<!--
This is a `.folded.md` archive — a directory packed into one markdown
file. The markers below are load-bearing; don't edit them directly.
To unpack (requires bash — if you have no shell, tell the user):
1. curl -sSLf https://fold.dom.vin/skill | bash -s <INSTALL_DIR>
2. <INSTALL_DIR>/fold/scripts/unfold <this-file>
(or: unfold <this-file> if fold/scripts is on your PATH)
-->
---
fold: true
marker: 4b96fb
at: 2026-05-07T16:16:20Z
root: seed-pack.iNhqfV
---
<!--fold:4b96fb@file path="README.md" mode="644"-->
# nextjs.app.router
## Assumptions
- you're using the Next.js App Router (not the Pages Router). server-component vs. client-component distinctions, layouts, and route handlers all assume App Router semantics.
- Node.js is installed and runnable. familiarity with React and TypeScript is assumed.
- you have or can create `next.config.{js,ts}` and `package.json`. dependencies will be installed via your package manager of choice.
Next.js App Router context for agents. What level to work at, what the Pages Router–trained agent gets wrong, and the stable mental model underneath version churn.
## What level — and what NOT to do
Work at the App Router level. Do not apply Pages Router patterns. Agents trained on Pages Router will reach for `getServerSideProps`, `useEffect` + `useState` for data fetching, and `pages/api/` routes. All three are wrong in the App Router. The mental model is inverted: **the server is the default, the client is the exception.**
Next.js changes rapidly. Caching behavior, async params, and bundling details have all shifted between major versions. Fetch the current docs from sources.md before making version-specific claims. Treat this file as orientation, not as the spec.
## The mental model: server components are the default
Every component in `app/` is a **server component** unless it has `"use client"` at the top. Server components:
- Run only on the server (they have zero JS bundle impact)
- Can be `async` and `await` directly — fetch from databases, call APIs, read the filesystem
- Cannot use `useState`, `useEffect`, event handlers, or browser APIs
**Client components** opt in with `"use client"`. They:
- Run in the browser (and also on the server for SSR hydration)
- Can use React hooks and browser APIs
- Cannot be `async`
- Cannot import server components (but can accept them as `children`)
**The boundary is where interactivity begins.** Push `"use client"` as far down the tree as possible. A button with an `onClick` needs to be a client component; the page that renders it doesn't.
## What agents get wrong
**1. Wrapping whole pages in `"use client"` for one interactive element.**
Extract just the interactive element into its own client component file. The surrounding page stays a server component.
**2. Fetching data in `useEffect` + `useState`.**
That's Pages Router. In App Router: make the component `async`, `await` the fetch directly inside it. No hook required.
**3. Putting client context providers in `layout.tsx` without wrapping them.**
`layout.tsx` is a server component. Providers like `ThemeProvider` or `SessionProvider` need `"use client"`. Create a `providers.tsx` client component and render it inside `layout.tsx` as a child.
**4. Importing a server component from inside a client component.**
This will fail. The pattern is: pass the server component as `children` into the client component. The server component renders on the server; the client component wraps it.
**5. Using `cookies()` or `headers()` outside of server components.**
These are server-only APIs. They work in server components, Route Handlers, and Server Actions. They do not work in client components.
**6. Confusing Route Handlers with Server Actions.**
Route Handlers (`app/api/foo/route.ts`) are HTTP endpoints — use them for external API consumers, webhooks, and non-form POST endpoints. Server Actions (`"use server"` functions) are for mutations triggered by React — form submissions, button actions. Don't reach for a Route Handler when a Server Action is the right shape.
**7. Assuming caching behavior from a prior version.**
The caching model has changed significantly across major versions. Fetch the upgrade guide from sources.md before asserting what gets cached and when.
## Stable facts — these survive version changes
- Server components are the default in `app/`
- `"use client"` marks the server/client boundary
- The boundary propagates downward: a client component's children are also treated as client unless explicitly split out
- Server components can import client components; client components cannot import server components
- `async`/`await` is the data-fetching primitive in server components
- Server Actions are marked with `"use server"` and can be called from client components
- `next/image` is the required image primitive — `<img>` bypasses optimization
## What AI is changing
**Automated component generation** works well for the structural boilerplate — page shells, route handler stubs, server action signatures. The failure mode is that code-generating models have Pages Router patterns in their training data and will apply them without being told otherwise. Use this seed to correct the baseline before generating.
**Vercel's AI SDK** (`ai` package) integrates tightly with App Router. Streaming responses use Route Handlers with the `StreamingTextResponse` or `streamText` pattern. Server Actions work for non-streaming AI calls. The AI SDK's patterns are App Router–native; don't port them to Pages Router conventions.
**What still requires human architectural judgment:**
- Where to draw the server/client boundary in a given feature
- Whether a mutation belongs in a Server Action or a Route Handler (the rule: Server Actions for UI-triggered mutations, Route Handlers for external API consumers)
- Cache invalidation strategy — when to use `revalidatePath`, `revalidateTag`, or on-demand ISR
- Route grouping and layout structure for a new feature area
- Whether to use parallel routes or intercepting routes for modal-style flows
These are architectural decisions, not code generation tasks. Get the structure right first, then generate the implementation.
<!--fold:4b96fb@file path="patterns.md" mode="644"-->
# patterns
Common App Router patterns, each with the minimal structure that makes it work.
## Server action for form submission
The right shape for a form that mutates data. No API route required.
```tsx
// app/actions.ts
"use server"
import { revalidatePath } from "next/cache"
export async function createItem(formData: FormData) {
const name = formData.get("name") as string
await db.item.create({ data: { name } })
revalidatePath("/items")
}
// app/items/new/page.tsx (server component — no "use client")
import { createItem } from "../actions"
export default function NewItemPage() {
return (
<form action={createItem}>
<input name="name" />
<button type="submit">Create</button>
</form>
)
}
```
If you need pending state or optimistic UI, extract the form into a client component and use `useFormStatus` or `useActionState`.
## Suspense boundary with streaming
Wrap slow data fetches so the page shell renders immediately while the data loads.
```tsx
// app/dashboard/page.tsx
import { Suspense } from "react"
import { SlowData } from "./SlowData"
export default function DashboardPage() {
return (
<main>
<h1>Dashboard</h1>
<Suspense fallback={<p>Loading…</p>}>
<SlowData />
</Suspense>
</main>
)
}
// app/dashboard/SlowData.tsx (async server component)
export async function SlowData() {
const data = await fetchSlowThing()
return <div>{data.value}</div>
}
```
The page shell renders immediately; `SlowData` streams in when the fetch resolves.
## Client component that receives server data
Don't fetch in the client component. Fetch in the server component and pass as props.
```tsx
// app/items/page.tsx (server component)
import { ItemList } from "./ItemList"
export default async function ItemsPage() {
const items = await db.item.findMany()
return <ItemList items={items} />
}
// app/items/ItemList.tsx (client component — needs useState for selection)
"use client"
export function ItemList({ items }: { items: Item[] }) {
const [selected, setSelected] = useState<string | null>(null)
// ...
}
```
## Providers in layout
Client context providers cannot go directly in `layout.tsx`. Wrap them.
```tsx
// app/providers.tsx
"use client"
import { ThemeProvider } from "next-themes"
export function Providers({ children }: { children: React.ReactNode }) {
return <ThemeProvider>{children}</ThemeProvider>
}
// app/layout.tsx (server component)
import { Providers } from "./providers"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}
```
## Route Handler
Use for external API consumers, webhooks, or anything that needs a real HTTP endpoint.
```ts
// app/api/items/route.ts
import { NextRequest } from "next/server"
export async function GET(request: NextRequest) {
const items = await db.item.findMany()
return Response.json(items)
}
export async function POST(request: NextRequest) {
const body = await request.json()
const item = await db.item.create({ data: body })
return Response.json(item, { status: 201 })
}
```
## Parallel routes (split layout slots)
Show two independent route segments in the same layout — e.g., a sidebar and a main panel that each load independently.
```
app/
layout.tsx — receives @sidebar and @main as props
@sidebar/
page.tsx
@main/
page.tsx
```
```tsx
// app/layout.tsx
export default function Layout({
sidebar,
main,
}: {
sidebar: React.ReactNode
main: React.ReactNode
}) {
return (
<div className="split">
<aside>{sidebar}</aside>
<section>{main}</section>
</div>
)
}
```
## Intercepting routes (modal pattern)
Show a modal when navigating client-side, but the full page when navigating directly (e.g. sharing the URL).
```
app/
items/
[id]/
page.tsx — full item page (direct navigation)
(.)items/
[id]/
page.tsx — intercepted route (modal, client navigation)
```
The `(.)` prefix intercepts the route at the same level. Use `(..)` for one level up.
## Cache invalidation
After a mutation, tell Next.js what to revalidate.
```ts
"use server"
import { revalidatePath, revalidateTag } from "next/cache"
// Revalidate a specific path
revalidatePath("/items")
// Revalidate everything tagged with "items" (fetch-level tagging)
revalidateTag("items")
```
Tag a fetch so it can be invalidated by tag later:
```ts
const data = await fetch("/api/items", { next: { tags: ["items"] } })
```
<!--fold:4b96fb@file path="sources.md" mode="644"-->
# sources
Fetch these at task time. Ordered by importance. Next.js changes fast — always pull from the live docs rather than relying on training data.
1. App Router documentation root — routing, rendering, data fetching, patterns:
https://nextjs.org/docs/app
2. Server vs Client components — the decision model, constraints, composition patterns:
https://nextjs.org/docs/app/building-your-application/rendering/server-components
3. Data fetching in App Router — async server components, fetch options, caching:
https://nextjs.org/docs/app/building-your-application/data-fetching/fetching
4. Server Actions — mutations, form handling, `"use server"` directive:
https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations
5. Upgrading from Pages Router — migration guide, what maps to what:
https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration
6. Latest version upgrade guide — breaking changes, cache model changes, async params:
https://nextjs.org/docs/app/building-your-application/upgrading
7. Vercel AI SDK — streaming, Server Actions with AI, App Router–native patterns:
https://sdk.vercel.ai/docs
<!--fold:4b96fb@end-->
PORTDOWN_03A71DE2
# ── post ──
MARKER=$(awk '/^---$/ { f++; if (f==2) exit; next } f==1 && /^marker:[[:space:]]/ { sub(/^marker:[[:space:]]+/, ""); print; exit }' "$DEST")
[ -z "$MARKER" ] && { echo "seed: archive has no marker — corrupt" >&2; exit 1; }
awk -v m="$MARKER" -v outdir="$TARGET" '
BEGIN {
# Match <!--fold:<m>@file path="X"--> with an optional mode attr after
# the path (fold emits mode="644" on executables).
file_re = "^<!--fold:" m "@file path=\"([^\"]+)\"( mode=\"[0-9]+\")?-->$"
end_re = "^<!--fold:" m "@end-->$"
}
$0 ~ end_re { if (current) close(current); exit }
$0 ~ file_re {
if (current) close(current)
line = $0
sub(/^<!--fold:[^@]+@file path="/, "", line); sub(/".*$/, "", line)
current = outdir "/" line
dir = current; sub(/\/[^\/]*$/, "", dir)
if (dir != current) system("mkdir -p \"" dir "\"")
printf "" > current
next
}
current { print >> current }
' "$DEST"
SEED_EXTRACTED=$(find "$TARGET" -type f -not -path "$DEST" 2>/dev/null | wc -l)
if [ "$SEED_EXTRACTED" = "0" ]; then
echo "seed: archive contained no files — refusing to delete the source" >&2
echo " archive preserved at: $DEST" >&2
exit 1
fi
rm -f "$DEST"
echo "" >&2
echo "✓ seed unpacked → $TARGET ($SEED_EXTRACTED files)" >&2
find "$TARGET" -type f | sort | while IFS= read -r _sf; do
echo " ${_sf#${TARGET}/}" >&2
done
echo "" >&2
if [ -f "$TARGET/SKILL.md" ]; then
echo "This seed contains a skill (SKILL.md). Install it in your agent's skills directory." >&2
echo "" >&2
fi
echo "Install the seed skill if not already installed:" >&2
echo " https://seed.show/skill" >&2
echo "" >&2
echo "Publisher prompt:" >&2
sed 's/^/ /' >&2 <<'__SEED_PROMPT_END_AC1F2B__'
You have the Next.js App Router context. Read README.md first — it covers what Pages Router patterns to avoid, the server/client boundary model, and what AI gets wrong. Read patterns.md for common App Router patterns with working code. Fetch sources.md for current docs before making version-specific claims. To share your own context bundles: seed.show/skill
__SEED_PROMPT_END_AC1F2B__
exit 0
You have the Next.js App Router context. Read README.md first — it covers what Pages Router patterns to avoid, the server/client boundary model, and what AI gets wrong. Read patterns.md for common App Router patterns with working code. Fetch sources.md for current docs before making version-specific claims. To share your own context bundles: seed.show/skill