agentleFS
peterbamuhigirepeterbamuhigire/chwezi-dev-engine/skills/frontend-ux/nextjs-app-router/SKILL.md

nextjs-app-router

Skill31 starsChanged 18 days agoMITView on GitHub

Use when implementing or reviewing a Next.js App Router application with server and client components, route handlers, Proxy migration, caching, authentication, streaming, or deployment. Use react-development for framework-neutral components and api-design-first for external APIs.

  • Reads credentials
  • Installs packages

14 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use skill-creator.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • SentrySkill

    skill-creator

    Create new agent skills following the Agent Skills specification. Use when asked to "create a skill", "add a new skill", "write a skill", "make a skill", "build a skill", or scaffold a new skill with SKILL.md. Guides through requirements, writing, registration, and verification.

    Also in 3 projects that use this

The file

We split this skill into the rules it gives an agent

10 rules. 0 are common, 3 are distinctive, and 7 are very specific to this project.

  • 0

    Common

    Most of the 12 edited versions we read keep these.

  • 3

    Distinctive

    This author's own choices. Worth a look.

  • 7

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use the links and companion skills already referenced in this file when deeper context is needed.

    line 32
  2. Distinctive:

    Treat each Server Action as a public endpoint: authenticate, authorize, validate, mutate, then revalidate only the affected path or tag.

    line 173
  3. Very specific:

    Do NOT 'use client' every component — server by default

    line 364
  4. Very specific:

    Do NOT use getServerSideProps/getStaticProps in App Router

    line 365
  5. Very specific:

    Do NOT store secrets in NEXT_PUBLIC_ vars

    line 366
  6. Very specific:

    Do NOT useEffect for data — fetch in server components

    line 367
  7. Distinctive:

    Do NOT create separate Express servers — use Route Handlers

    line 368
  8. Very specific:

    Do NOT await fetches sequentially — use Promise.all

    line 369
  9. Very specific:

    Do NOT skip loading.tsx — every dynamic route needs a Suspense boundary

    line 370
  10. Very specific:

    Read and search the application first.

    line 387

“Common” is measured: kept by at least 6 in 10 of the 12 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Next.js App Router Patterns

Acknowledgement: Shared by Peter Bamuhigire, techguypeter.com, +256 784 464178.

<!-- dual-compat-start -->

Chwezi Product Excellence Standard (mandatory)

Any Chwezi app or system UI work (effective 01 Nov 2026) MUST follow the Chwezi Product Excellence Standard in chwezi-design-engine (resolve via the engine-routing table; never hard-code the path): doctrine/product-excellence-standard.md and skill skills/00-cross-cutting-ops-qa-a11y/chwezi-product-excellence-standard. Engineering gates: product-excellence-engineering-gates.md. Authorise every page, Route Handler and Server Action on the server (PX-GATE-04), keep queued and confirmed states distinct, and meet Core Web Vitals at p75 on a low-end mobile profile. Where this skill and the standard differ, the standard wins.

Use When

  • Next.js App Router patterns for production — server/client components, parallel routes, Proxy migration, server-side authorization, caching, background jobs (BullMQ), data fetching, authentication, deployment, and CI/CD.

Evidence Produced

CategoryArtifactFormatExample
CorrectnessRoute + middleware test planMarkdown doc covering server/client component boundaries, parallel routes, and middlewaredocs/web/nextjs-route-tests.md
SecurityRBAC three-tier configuration noteMarkdown doc covering middleware / server action / data-access guardsdocs/web/nextjs-rbac.md

References

  • Use the links and companion skills already referenced in this file when deeper context is needed.
  • For private tenant-owned data and auth/cache boundaries, load references/tenant-data-security.md.
<!-- dual-compat-end -->

Project Setup

npx create-next-app@latest my-app \
  --typescript --tailwind --eslint --app --src-dir

Folder Structure (App Router)

app/
├── layout.tsx          # Root layout (required)
├── page.tsx            # Home route /
├── (marketing)/        # Route group — no URL segment
├── @notifications/     # Parallel route slot
│   └── page.tsx
├── dashboard/
│   ├── layout.tsx      # Nested layout
│   ├── page.tsx
│   ├── loading.tsx     # Suspense fallback
│   ├── error.tsx       # Error boundary ('use client')
│   └── not-found.tsx
└── api/users/route.ts  # GET/POST /api/users

Server vs Client Components

// Server component (default) — async, zero JS to client
// Public, non-sensitive example only. For user-specific data, authenticate at
// the data-access boundary and avoid shared/static caching; see the tenant
// data security reference below.
export default async function UsersPage() {
  const users = await fetch('https://api.example.com/users', {
    cache: 'force-cache',        // static
    // next: { revalidate: 60 } // ISR
    // cache: 'no-store'        // SSR
  }).then(r => r.json());
  return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

// Client component — needs interactivity, browser APIs
'use client';
export default function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>;
}

Rule: Push 'use client' as far down the tree as possible.


Routing

FileRoute
app/page.tsx/
app/blog/[slug]/page.tsx/blog/:slug
app/shop/[...slug]/page.tsx/shop/* catch-all
app/(auth)/login/page.tsx/login (route group)
app/@modal/page.tsxParallel slot

Dynamic Routes + generateStaticParams

export default async function BlogPost({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  const post = await fetchPost(slug);
  return <article><h1>{post.title}</h1></article>;
}
export async function generateStaticParams() {
  const posts = await fetchAllPosts();
  return posts.map(p => ({ slug: p.slug }));
}

Parallel Routes (@slot)

// app/support/layout.tsx — load @tickets and @chat independently
export default function SupportLayout({ tickets, chat }: {
  tickets: React.ReactNode; chat: React.ReactNode;
}) {
  return (
    <div className="grid grid-cols-2 gap-4">
      <aside>{tickets}</aside>
      <main>{chat}</main>
    </div>
  );
}
// Directories: app/support/@tickets/page.tsx  app/support/@chat/page.tsx
// Use case: dashboards, live feeds, multi-pane UIs loading at different speeds

Data Fetching

ISR Revalidation Guidelines

Content TypeRevalidatePattern
Blog / docs60snext: { revalidate: 60 }
News feed10snext: { revalidate: 10 }
Products / pricing300snext: { revalidate: 300 }
Static marketing86400scache: 'force-cache'
User-specificDynamiccache: 'no-store'
Real-time (stock)N/ASSR or WebSocket

Parallel Fetch + React cache()

// Parallel fetching
const [user, posts] = await Promise.all([
  fetch('/api/user').then(r => r.json()),
  fetch('/api/posts').then(r => r.json()),
]);

// Deduplication for POST/GraphQL requests
import { cache } from 'react';
export const getUser = cache(async (id: string) => db.user.findUnique({ where: { id } }));

Route Handlers (API Routes)

Route Handlers are public entry points. Authenticate and authorize in the data-access function on every request; never return an unscoped table scan. See references/tenant-data-security.md for the complete read-only example.


Server Actions

Treat each Server Action as a public endpoint: authenticate, authorize, validate, mutate, then revalidate only the affected path or tag. Do not rely on a hidden UI control or page-level check for authorization.

Server Actions vs API Routes

UseWhen
Server ActionForm submissions, mutations from UI, same-app data changes
API RoutePublic APIs, webhooks, third-party consumers, complex error handling

Proxy and request routing

// proxy.ts — project root. A redirect is only a navigation aid.
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function proxy(request: NextRequest) {
  const { pathname } = request.nextUrl;
  const hasSessionCookie = Boolean(request.cookies.get('session')?.value);
  if (!hasSessionCookie && pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
  return NextResponse.next();
}

export const config = { matcher: ['/dashboard/:path*', '/api/:path*'] };

Authorization and tenant data access

Proxy may perform an optimistic redirect from the presence of a session cookie.
It must not trust a role cookie, establish tenant membership, or replace a
server-side authorization check. Verify the session and permission in a
server-only data-access layer for each page, Route Handler, and Server Action.
Apply the verified tenant predicate in the database query and return an
allowlisted DTO. See `references/tenant-data-security.md` for a complete
read-only invoice path and negative cases.

Redis Caching

// lib/redis.ts
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);
export default redis;

User-specific results must not use a shared cache key or a shared static
render. Establish authorization before reading data. If a cache is introduced,
prove key isolation, invalidation, expiry, and failure behaviour for the
verified tenant and permission context; otherwise keep the path uncached and
private. Cache invalidation is not an authorization control.

Background Jobs (BullMQ)

// lib/queue.ts
import { Queue, Worker } from 'bullmq';
import Redis from 'ioredis';
const connection = new Redis(process.env.REDIS_URL!);
export const emailQueue = new Queue('email', { connection });

// Worker (separate process or dedicated route)
new Worker('email', async (job) => {
  const { to, subject, body } = job.data;
  await sendEmail(to, subject, body);
}, { connection });

// Enqueue from API route
export async function POST(req: NextRequest) {
  const { email } = await req.json();
  await emailQueue.add('welcome', { to: email, subject: 'Welcome!' });
  return Response.json({ queued: true });
}

Authentication (NextAuth v5)

// auth.ts
export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [GitHub({ clientId: process.env.GITHUB_ID!, clientSecret: process.env.GITHUB_SECRET! })],
  callbacks: {
    jwt({ token, user }) { if (user) token.role = user.role; return token; },
    session({ session, token }) { session.user.role = token.role as string; return session; },
  },
});

Database (Prisma Singleton)

// lib/prisma.ts — prevents connection exhaustion in dev HMR
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
export const db = globalForPrisma.prisma || new PrismaClient({ log: ['query'] });
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = db;

Database Selection

DatabaseUse When
PostgreSQL + PrismaRelational data, transactions, production default
MongoDB + MongooseDocument/flexible schemas, content management
Firebase FirestoreReal-time sync, serverless, mobile-first

CI/CD (GitHub Actions)

name: CI/CD
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with: { node-version: '24.x' }
      - run: npm ci
      - run: npm run lint
      - run: npm test
      - run: npm run build
  deploy:
    needs: build
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - run: npm install -g vercel && vercel --prod --token=${{ secrets.VERCEL_TOKEN }}

Deployment

PlatformBest For
VercelZero-config, global CDN, Edge Network, native Next.js
RailwaySimple self-hosted, easy DB provisioning
AWS AmplifyFull AWS ecosystem, enterprise
Docker/Self-hostedFull control, no vendor lock-in
# next.config.js: module.exports = { output: 'standalone' }
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
CMD ["node", "server.js"]

Built-in Components

import Image from 'next/image';
import Link from 'next/link';

<Image src="/hero.jpg" alt="Hero" width={800} height={600} priority />
<Link href="/dashboard">Dashboard</Link>
<Link href={`/blog/${slug}`} prefetch={false}>Post</Link>

Anti-Patterns

  • Do NOT 'use client' every component — server by default
  • Do NOT use getServerSideProps/getStaticProps in App Router
  • Do NOT store secrets in NEXT_PUBLIC_ vars
  • Do NOT useEffect for data — fetch in server components
  • Do NOT create separate Express servers — use Route Handlers
  • Do NOT await fetches sequentially — use Promise.all
  • Do NOT skip loading.tsx — every dynamic route needs a Suspense boundary

Sources: Rambert — Advanced Next.js for Everyone (2024); Kim — The Next.js Handbook (2023); Jain — Modern Web Applications with Next.js (2024); Krause — The Complete Developer (2024)

Decision rules

ConditionChoiceFailure avoided
No browser API or interaction is neededKeep a Server ComponentUnnecessary client JavaScript
Independent data blocks a routeFetch concurrently and stream non-critical regionsSequential latency
Mutation is application-internalUse a validated, authorised Server ActionDuplicate transport plumbing
Contract serves external consumersUse a Route Handler with an explicit contractHidden coupling

Capability contract

Read and search the application first. Edit only when authorised; execute the existing type-check, tests, build, and route smoke checks when available. Network access is optional for current Next.js documentation.

Inputs

ArtefactRequired?Purpose
Next.js version, route requirements, data/cache boundaries, auth, and deployment targetyesSelect App Router patterns

Outputs

  • Produce Next.js implementation or design with route, cache, error, test, and deployment evidence.

Degraded mode

Fallback without a runnable Next.js environment: provide route/cache contracts and mark runtime rendering and invalidation unverified.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.