Nextjs

Next.js App Router best practices — Server Components, data fetching, caching, routing, middleware, metadata, error handling, streaming, Server Actions, and performance optimization for Next.js 14-16+.

MIT-0 · Free to use, modify, and redistribute. No attribution required.
0 · 791 · 2 current installs · 2 all-time installs
MIT-0
Security Scan
VirusTotalVirusTotal
Benign
View report →
OpenClawOpenClaw
Benign
high confidence
Purpose & Capability
Name/description (Next.js App Router guidance) align with the content: large SKILL.md and many reference docs containing patterns, code snippets, and deployment guidance. The skill requests no binaries, env vars, or installs, which is proportionate for a documentation/guide skill.
Instruction Scope
Runtime instructions are guidance-only (examples, code snippets, and an install hint for 'npx clawhub install nextjs'). Reference files include examples that access process.env (e.g., REDIS_URL, CACHE_BUCKET, DATABASE_URL) and third-party services (Cloudinary, Google Analytics) but these are demonstrative for deployment examples rather than directives for the agent to read local secrets or exfiltrate data. Reviewers should be aware copying examples into production without validating env handling can leak secrets, but the skill itself does not instruct the agent to access unrelated system files or secrets.
Install Mechanism
No install spec and no code to download/execute. Instruction-only skills are lowest-risk from an install mechanism perspective.
Credentials
The skill declares no required environment variables or credentials (none in requires.env). Several sample snippets and deployment examples reference common env names (REDIS_URL, AWS_REGION, CACHE_BUCKET, DATABASE_URL, API_SECRET). These are reasonable illustrative examples for a Next.js guide but are not required by the skill; users should not paste these examples into a project without supplying appropriate, secure credentials.
Persistence & Privilege
Flags show always:false and no install actions that modify system or other skills. disable-model-invocation is false (agent can invoke autonomously) which is the platform default; this is not combined with any other persistent or privileged behavior.
Assessment
This skill is a documentation-style, instruction-only guide for Next.js App Router and is internally consistent with that purpose. It does include deployment and integration examples that show env variable names and connectors (Redis, S3/AWS, analytics/CDNs). That is normal for a guide — but before you copy-run any example code, make sure you: (1) do not paste secrets into public repos or consoles, (2) provide real credentials only in secure runtime environments, and (3) review any third‑party service URLs or SDK usage to avoid unintended data exposure. If you want extra caution, inspect the exact snippets you plan to use and test them in an isolated environment first.

Like a lobster shell, security has layers — review code before you run it.

Current versionv1.0.0
Download zip
latestvk976dzc5tf5svq3xcg2223cy5x80w9b8

License

MIT-0
Free to use, modify, and redistribute. No attribution required.

SKILL.md

Next.js App Router

Apply these patterns when building, reviewing, or debugging Next.js App Router applications.

Installation

OpenClaw / Moltbot / Clawbot

npx clawhub@latest install nextjs

WHEN

  • Building Next.js applications with App Router
  • Migrating from Pages Router to App Router
  • Implementing Server Components and streaming
  • Setting up parallel and intercepting routes
  • Optimizing data fetching and caching
  • Building full-stack features with Server Actions
  • Debugging hydration errors or RSC boundary issues

Rendering Modes

ModeWhereWhen to Use
Server ComponentsServer onlyData fetching, secrets, heavy computation
Client ComponentsBrowserInteractivity, hooks, browser APIs
Static (SSG)Build timeContent that rarely changes
Dynamic (SSR)Request timePersonalized or real-time data
StreamingProgressiveLarge pages, slow data sources

Server vs Client Decision Tree

Does it need...?
├── useState, useEffect, event handlers, browser APIs
│   └── Client Component ('use client')
├── Direct data fetching, no interactivity
│   └── Server Component (default)
└── Both?
    └── Split: Server parent fetches data → Client child handles UI

File Conventions

See file-conventions.md for complete reference.

app/
├── layout.tsx          # Shared UI wrapper (persists across navigations)
├── page.tsx            # Route UI
├── loading.tsx         # Suspense fallback (automatic)
├── error.tsx           # Error boundary (must be 'use client')
├── not-found.tsx       # 404 UI
├── route.ts            # API endpoint (cannot coexist with page.tsx)
├── template.tsx        # Like layout but re-mounts on navigation
├── default.tsx         # Parallel route fallback
└── opengraph-image.tsx # OG image generation

Route segments: [slug] dynamic, [...slug] catch-all, [[...slug]] optional catch-all, (group) route group, @slot parallel route, _folder private (excluded from routing).

Data Fetching Patterns

Choose the right pattern for each use case. See data-patterns.md for full decision tree.

PatternUse CaseCaching
Server Component fetchInternal reads (preferred)Full Next.js caching
Server ActionMutations, form submissionsPOST only, no cache
Route HandlerExternal APIs, webhooks, public RESTGET can be cached
Client fetch → APIClient-side reads (last resort)HTTP cache headers

Server Component Data Fetching (Preferred)

// app/products/page.tsx — Server Component by default
export default async function ProductsPage() {
  const products = await db.product.findMany() // Direct DB access, no API layer
  return <ProductGrid products={products} />
}

Avoiding Data Waterfalls

// BAD: Sequential — each awaits before the next starts
const user = await getUser()
const posts = await getPosts()

// GOOD: Parallel fetching
const [user, posts] = await Promise.all([getUser(), getPosts()])

// GOOD: Streaming with Suspense — each section loads independently
<Suspense fallback={<UserSkeleton />}><UserSection /></Suspense>
<Suspense fallback={<PostsSkeleton />}><PostsSection /></Suspense>

Server Actions (Mutations)

// app/actions.ts
'use server'
import { revalidateTag } from 'next/cache'

export async function addToCart(productId: string) {
  const cookieStore = await cookies()
  const sessionId = cookieStore.get('session')?.value
  if (!sessionId) redirect('/login')

  await db.cart.upsert({
    where: { sessionId_productId: { sessionId, productId } },
    update: { quantity: { increment: 1 } },
    create: { sessionId, productId, quantity: 1 },
  })
  revalidateTag('cart')
  return { success: true }
}

Caching Strategy

MethodSyntaxUse Case
No cachefetch(url, { cache: 'no-store' })Always-fresh data
Staticfetch(url, { cache: 'force-cache' })Rarely changes
ISRfetch(url, { next: { revalidate: 60 } })Time-based refresh
Tag-basedfetch(url, { next: { tags: ['products'] } })On-demand invalidation

Invalidate from Server Actions:

'use server'
import { revalidateTag, revalidatePath } from 'next/cache'

export async function updateProduct(id: string, data: ProductData) {
  await db.product.update({ where: { id }, data })
  revalidateTag('products')   // Invalidate by tag
  revalidatePath('/products') // Invalidate by path
}

RSC Boundaries

Props crossing Server → Client boundary must be JSON-serializable. See rsc-boundaries.md.

Prop TypeValid?Fix
string, number, booleanYes
Plain object / arrayYes
Server Action ('use server')Yes
Function () => {}NoDefine inside client component
Date objectNoUse .toISOString()
Map, Set, class instanceNoConvert to plain object/array

Critical rule: Client Components cannot be async. Fetch data in a Server Component parent and pass it down.

Async APIs (Next.js 15+)

params, searchParams, cookies(), and headers() are all async. See async-patterns.md.

// Pages and layouts — always await params
type Props = { params: Promise<{ slug: string }> }

export default async function Page({ params }: Props) {
  const { slug } = await params
}

// Server functions
const cookieStore = await cookies()
const headersList = await headers()

// Non-async components — use React.use()
import { use } from 'react'
export default function Page({ params }: Props) {
  const { slug } = use(params)
}

Routing Patterns

Route Organization

PatternSyntaxPurpose
Route groups(marketing)/Organize without affecting URL
Parallel routes@analytics/Multiple independent sections in one layout
Intercepting routes(.)photos/[id]Modal overlays on soft navigation
Private folders_components/Exclude from routing

Parallel Routes & Modals

See parallel-routes.md for complete modal pattern.

Key rules:

  • Every @slot folder must have a default.tsx (returns null) or you get 404 on refresh
  • Close modals with router.back(), never router.push() or <Link>
  • Intercepting route matchers: (.) same level, (..) one level up, (...) from root

Metadata & SEO

See metadata.md for OG images, sitemaps, and file conventions.

// Static metadata (layout or page)
export const metadata: Metadata = {
  title: { default: 'My App', template: '%s | My App' },
  description: 'Built with Next.js',
}

// Dynamic metadata
export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params
  const post = await getPost(slug)
  return {
    title: post.title,
    description: post.description,
    openGraph: { images: [{ url: post.image, width: 1200, height: 630 }] },
  }
}

Metadata is Server Components only. If a page has 'use client', extract metadata to a parent layout.

Error Handling

See error-handling.md for full patterns including auth errors.

// app/blog/error.tsx — must be 'use client'
'use client'
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={() => reset()}>Try again</button>
    </div>
  )
}

Critical gotcha: redirect(), notFound(), forbidden(), and unauthorized() throw special errors. Never catch them in try/catch:

// BAD: redirect throw is caught — navigation fails!
try {
  await db.post.create({ data })
  redirect(`/posts/${post.id}`)
} catch (error) {
  return { error: 'Failed' } // Catches the redirect too!
}

// GOOD: Call redirect outside try-catch
let post
try { post = await db.post.create({ data }) }
catch (error) { return { error: 'Failed' } }
redirect(`/posts/${post.id}`)

Streaming with Suspense

export default async function ProductPage({ params }: Props) {
  const { id } = await params
  const product = await getProduct(id) // Blocking — loads first

  return (
    <div>
      <ProductHeader product={product} />
      <Suspense fallback={<ReviewsSkeleton />}>
        <Reviews productId={id} />       {/* Streams in independently */}
      </Suspense>
      <Suspense fallback={<RecommendationsSkeleton />}>
        <Recommendations productId={id} /> {/* Streams in independently */}
      </Suspense>
    </div>
  )
}

Hooks That Require Suspense Boundaries

HookSuspense Required
useSearchParams()Always (or entire page becomes CSR)
usePathname()In dynamic routes
useParams()No
useRouter()No

Performance

  • Always use next/image over <img> — see image-optimization.md
  • Always use next/link over <a> — client-side navigation with prefetching
  • Always use next/font — see font-optimization.md
  • Always use next/script — see scripts.md
  • Set priority on above-the-fold images (LCP)
  • Add sizes when using fill — without it, the largest image variant downloads
  • Dynamic imports for heavy client components: const Chart = dynamic(() => import('./Chart'))
  • Use generateStaticParams to pre-render dynamic routes at build time

Route Handlers

See route-handlers.md for API endpoint patterns.

Bundling

See bundling.md for fixing third-party package issues, server-incompatible packages, and ESM/CommonJS problems.

Hydration Errors

See hydration-errors.md for all causes and fixes.

CauseFix
Browser APIs (window, localStorage)Client component with useEffect mount check
new Date().toLocaleString()Render on client with useEffect
Math.random() for IDsUse useId() hook
<p><div>...</div></p>Fix invalid HTML nesting
Third-party scripts modifying DOMUse next/script with afterInteractive

Self-Hosting

See self-hosting.md for Docker, PM2, cache handlers, and deployment checklist.

Key points:

  • Use output: 'standalone' for Docker — creates minimal production bundle
  • Copy public/ and .next/static/ separately (not included in standalone)
  • Set HOSTNAME="0.0.0.0" for containers
  • Multi-instance ISR requires a custom cache handler (Redis/S3) — filesystem cache breaks
  • Set health check endpoint at /api/health

NEVER Do

NeverWhyInstead
Add 'use client' by defaultBloats client bundle, loses Server Component benefitsServer Components are default — add 'use client' only for interactivity
Make client components asyncNot supported — will crashFetch in Server Component parent, pass data as props
Pass Date/Map/functions to clientNot serializable across RSC boundarySerialize to string/plain object, or use Server Actions
Fetch from own API in Server ComponentsUnnecessary round-trip — you're already on the serverAccess DB/service directly
Wrap redirect()/notFound() in try-catchThey throw special errors that get swallowedCall outside try-catch or use unstable_rethrow()
Skip loading.tsx or Suspense fallbacksUsers see blank page during data loadingAlways provide loading states
Use useSearchParams without SuspenseEntire page silently falls back to CSRWrap in <Suspense> boundary
Use router.push() to close modalsBreaks history, modal can flash/persistUse router.back()
Use @vercel/og for OG imagesBuilt into Next.js alreadyImport from next/og
Omit default.tsx in parallel route slotsHard navigation (refresh) returns 404Add default.tsx returning null
Use Edge runtime unless requiredLimited APIs, most npm packages breakDefault Node.js runtime covers 95% of cases
Skip sizes prop on fill imagesDownloads largest image variant alwaysAdd sizes="100vw" or appropriate breakpoints
Import fonts in multiple componentsCreates duplicate instancesImport once in layout, use CSS variable
Use <link> for Google FontsNo optimization, blocks renderingUse next/font

Reference Files

FileTopic
rsc-boundaries.mdServer/Client boundary rules, serialization
data-patterns.mdFetching decision tree, waterfall avoidance
error-handling.mdError boundaries, redirect gotcha, auth errors
async-patterns.mdNext.js 15+ async params/cookies/headers
metadata.mdSEO, OG images, sitemaps, file conventions
parallel-routes.mdModal pattern, intercepting routes, gotchas
hydration-errors.mdCauses, debugging, fixes
self-hosting.mdDocker, PM2, cache handlers, deployment
file-conventions.mdProject structure, special files, middleware
bundling.mdThird-party packages, SSR issues, Turbopack
image-optimization.mdnext/image best practices
font-optimization.mdnext/font best practices
scripts.mdnext/script, third-party loading
route-handlers.mdAPI endpoints, request/response helpers

Files

16 total
Select a file
Select a file to preview.

Comments

Loading comments…