Skip to content
Development
Agent

nextjs-app-router

**Scope**: App Router patterns for portfolio sites: Server vs Client components, metadata API, static generation, data fetching. **Version range**: Next.js 13.4+ (App Router stable), 14+ recommended **Generated**: 2026-04-12

From plugin
vexjoy-agent
449198 skills198 agents12 commands83 hooks

Claude Code

$ npx -y skills add notque/vexjoy-agent --agent claude-code

Agent definition

nextjs-app-router.md

Next.js App Router Reference

> **Scope**: App Router patterns for portfolio sites: Server vs Client components, metadata API, static generation, data fetching. > **Version range**: Next.js 13.4+ (App Router stable), 14+ recommended > **Generated**: 2026-04-12

---

Pattern Table

| Pattern | Version | Use When | Avoid When | |---------|---------|----------|------------| | `generateStaticParams` | 13.4+ | Gallery pages with known slugs | Dynamic user input | | `generateMetadata` | 13.4+ | Per-artwork SEO/OG images | Static metadata for all pages | | `export const dynamic = 'force-static'` | 13.4+ | Page must never run server-side | Page reads cookies/headers | | `<Suspense>` with `loading.tsx` | 13.4+ | Data fetching in Server Components | Non-async content | | `unstable_cache` | 14.0+ | Repeated CMS/API fetches | One-off requests |

---

Correct Patterns

Server Component for Gallery Grid

Gallery grids render same content for all visitors. No `'use client'`, fetch directly, zero bundle cost.

// app/gallery/page.tsx — Server Component
export default async function GalleryPage() {
  const artworks = await getArtworks()
  return <GalleryGrid artworks={artworks} />
}

---

Isolate Interactivity at the Leaf

Push `'use client'` as deep as possible to preserve static generation.

// components/ArtworkCard.tsx — Server Component
import { LightboxTrigger } from './LightboxTrigger' // Client Component

export function ArtworkCard({ artwork }: { artwork: Artwork }) {
  return (
    <article>
      <Image src={artwork.src} alt={artwork.alt} width={600} height={400} />
      <h2>{artwork.title}</h2>
      <LightboxTrigger artworkId={artwork.id} />
    </article>
  )
}

---

Per-Artwork Metadata with generateMetadata

// app/gallery/[slug]/page.tsx
export async function generateMetadata({ params }): Promise<Metadata> {
  const artwork = await getArtworkBySlug(params.slug)
  return {
    title: `${artwork.title} — Portfolio`,
    description: artwork.description,
    openGraph: { images: [{ url: artwork.src, width: 1200, height: 630 }] },
  }
}

export async function generateStaticParams() {
  const artworks = await getArtworkBySlug('*')
  return artworks.map(art => ({ slug: art.slug }))
}

---

Pattern Catalog

Keep Pages as Server Components

**Detection**:

grep -rn "'use client'" app/ --include="*.tsx" | grep "page.tsx"

Entire page as Client Component disables static generation, ships all metadata as JS. Extract `useState` into a small Client Component leaf.

---

Sync Filter State with URL Search Params

**Detection**:

rg "setFilter\|activeFilter" --type tsx

In-memory filter state disappears on refresh, breaks sharing/back button.

**Preferred action**: Use `useSearchParams` + `useRouter`:

const active = searchParams.get('category') ?? 'all'
const setFilter = (cat: string) => {
  const params = new URLSearchParams(searchParams)
  if (cat === 'all') params.delete('category')
  else params.set('category', cat)
  router.push(`?${params.toString()}`, { scroll: false })
}

**Note**: `useSearchParams` requires `<Suspense>` in Next.js 14+.

---

Fetch Data in Async Server Components

**Detection**:

grep -rn "getServerSideProps\|getStaticProps" app/ --include="*.tsx"

These are Pages Router APIs — silently ignored in App Router. Fetch in the async component body:

export default async function GalleryPage() {
  const artworks = await fetch('...').then(r => r.json())
  return <GalleryGrid artworks={artworks} />
}

---

Error-Fix Mappings

| Error Message | Root Cause | Fix | |---------------|------------|-----| | `useState is not a function` in Server Component | Missing `'use client'` | Add directive or extract to Client Component | | `useSearchParams() should be wrapped in suspense` | Next.js 14 requirement | Wrap with `<Suspense>` | | `Each child should have a unique "key" prop` | Missing key on mapped elements | Add `key={artwork.id}` | | `Image detected as LCP` without `priority` | Hero not marked priority | Add `priority` prop | | `Un-configured Host` | External domain not in config | Add to `images.remotePatterns` |

---

Version-Specific Notes

| Version | Change | Impact | |---------|--------|--------| | 13.4 | App Router stable | Migrate from Pages Router | | 14.0 | `useSearchParams` requires Suspense | Wrap filter components | | 14.1 | `unstable_cache` for CMS dedup | Use for Sanity/Contentful | | 15.0 | Partial Prerendering experimental | Galleries with dynamic sections |

---

Detection Commands Reference

grep -rn "'use client'" app/ --include="*.tsx" | grep "page.tsx"
grep -rn "getServerSideProps\|getStaticProps" app/ --include="*.tsx"
rg "useState.*category|useState.*filter" --type tsx
grep -rn "useSearchParams" --include="*.tsx" | grep -v "Suspense"

---

See Also

  • `image-optimization.md` — next/image props, WebP/AVIF, blur placeholders
  • `performance.md` — Core Web Vitals, LCP, bundle analysis
Read more
@notque
Ships withvexjoy-agent

Essays and writing behind this toolkit live at vexjoy.com. VexJoy Agent connects plain-English requests to specialist agents, skills, and workflows. /do selects the knowledge and tools needed for your task.

44964 skills198 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
449
Star history of notque/vexjoy-agent on GitHub, a chart by star-history.com

Other agents on vexjoy-agent.

modules

modules

Agent

**Scope**: Module selection patterns, builtin vs command/shell decisions, collection modules, and version-specific module changes…

@notque@notqueInstall Agent
testing

testing

Agent

**Scope**: Molecule test scenarios, ansible-lint rules, idempotency validation, and check-mode patterns **Version range**:…

@notque@notqueInstall Agent
communication-patterns

communication-patterns

Agent

**Scope**: Failure modes in agent output style — over-reporting, self-congratulation, verbose narration, and hedging. Covers what…

@notque@notqueInstall Agent

More agents from other plugins.

a11y-architect
Auto-invoked

a11y-architect

Agent

Accessibility Architect specializing in WCAG 2.2 compliance for Web and Native platforms. Use PROACTIVELY when designing UI…

ecc13d293k
@affaan-m@affaan-mInstall Agent
analyze-pass-impact

analyze-pass-impact

Agent

Analyzes how a specific topic affects a group of compiler passes. Used by the /plan-update skill to parallelize research across…

react2d252k
@facebook@facebookInstall Agent
orchestrator

orchestrator

Agent

- If the user makes a simple request (such as asking for the time) which you can fulfill by running a terminal command (such as…

codex2d135k
@openai@openaiInstall Agent
cavecrew-builder

cavecrew-builder

Agent

Surgical 1-2 file edit. Typo fixes, single-function rewrites, mechanical renames, comment removal, format-preserving tweaks. Hard…

caveman3d119k
@juliusbrussee@juliusbrusseeInstall Agent

Questions about nextjs-app-router.

Which coding agents can use nextjs-app-router?
nextjs-app-router installs in Claude Code and Cursor: each loads subagents from .claude/agents, so copy the file there. Flowy's auto-invocation runs in Claude Code only.