skills/frontend/nextjs-specialist/SKILL.md
Next.js specialist knowledge — App Router architecture, Server Components, Server Actions, rendering strategies, caching, routing, API route handlers, and optimization patterns for Next.js 14+.
npx skillsauth add devjarus/coding-agent nextjs-specialistInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Deep expertise in Next.js 14+ App Router, Server Components, Server Actions, rendering strategies, and full-stack data patterns.
page.tsx, layout.tsx, loading.tsx, error.tsx, route.ts'use client' only for browser APIs/state/effectsrevalidatePath()/revalidateTag()unstable_cache, React cache(), tag-based invalidation<Link>, useRouter(), middleware, dynamic segmentsnext/image, next/font, <Script>, metadata exportNEXT_PUBLIC_ for client, plain for server-onlyPromise.all() for independent concurrent fetches in Server Componentserror.tsx at every route segment with independent failure modesloading.tsx at segments whose data takes perceptibly longpage.tsx must export metadata or generateMetadataThese bite real projects and produce runtime errors, not typecheck errors — easy to miss in smoke tests. Always navigate actual routes in the evaluator's runtime verification, not just curl /.
In Next.js 15+, dynamic segment params and searchParams are Promises. Must await them before use in both page.tsx and route.ts:
// app/notes/[...slug]/page.tsx
export default async function NotePage({ params }: { params: Promise<{ slug: string[] }> }) {
const { slug } = await params; // REQUIRED: await first
const path = slug.join("/");
// ...
}
// app/api/notes/[...slug]/route.ts
export async function GET(
req: Request,
{ params }: { params: Promise<{ slug: string[] }> }
) {
const { slug } = await params; // REQUIRED: await first
// ...
}
Forgetting the await produces cryptic runtime errors like Cannot read properties of undefined (reading 'join'), NOT typecheck errors.
dynamic(() => ..., { ssr: false }) cannot be called from server componentsIn Next.js 15, next/dynamic with ssr: false is forbidden inside server components. The working pattern: a tiny client-component wrapper whose only job is to host the dynamic() call:
// app/components/command-palette-loader.tsx
'use client';
import dynamic from 'next/dynamic';
const CommandPalette = dynamic(() => import('./command-palette'), { ssr: false });
export default function CommandPaletteLoader() {
return <CommandPalette />;
}
// app/layout.tsx (server component)
import CommandPaletteLoader from './components/command-palette-loader';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<CommandPaletteLoader />
{children}
</body>
</html>
);
}
The rest of the shell layout stays a server component. Only the loader is client.
next-themes injects an IIFE into <body> that reads localStorage (or prefers-color-scheme) and calls document.documentElement.classList.add('dark') before React hydrates. Without this script, there's a flash of light mode on first paint even when the user prefers dark.
Three things are required for this to work:
<html suppressHydrationWarning> on the root — the IIFE mutates classList before React runs, which would normally trigger a hydration warning<ThemeProvider> from next-themes wrapping the appcurl -s http://localhost:<port>/ | grep -c 'classList.add')Verify dark-mode-won't-flash by inspecting the served HTML, not just the React tree. Missing IIFE = FOUC on every first paint.
next dev silently falls back to port 3001 (or the next available) if 3000 is taken, and only logs it once in the initial output. Never hardcode 3000 in evaluator scripts — parse the actual port from stderr. Curling the wrong port will hit whatever other process owns 3000, which is almost always misleading.
pnpm dev wipes .next/ on restartIf you verify a specific compiled chunk (e.g., a code-split file 807.<hash>.js) and then start pnpm dev, the chunk filename changes and the file you verified no longer exists. Verify build artifacts BEFORE starting the dev server, not after. Applies equally to any framework with a dev pipeline (Vite, Astro, webpack-dev-server).
'use client' only when needed<img> tagstesting
Multi-source research method — decompose a question, fan out parallel investigators, interleaved-think each result, verify claims adversarially, synthesize a cited answer. Use for breadth-heavy research, stack comparisons, "which approach wins" questions.
testing
Decide when to use unit vs integration vs e2e tests, and when to mock vs use the real thing per dependency. Dependency injection is the enabler — without it you end up monkey-patching imports. Apply when writing tests of any kind.
development
Test-driven development process — write failing test, implement to pass, refactor. Use when implementing any feature or fixing bugs.
development
Patterns for sharing types, API contracts, and validation schemas between frontend and backend. Use when multiple domains consume the same data shapes to prevent contract drift.