maroffo

react-nextjs

"React 19 + Next.js 16 App Router development. Use when working with .tsx/.jsx files, next.config, or user asks about Server Components, data fetching, state management, forms, or React testing."

maroffo 13 2 Updated 3mo ago
GitHub

Install

npx skillscat add maroffo/claude-forge/react-nextjs

Install via the SkillsCat registry.

SKILL.md

ABOUTME: React 19 + Next.js 16 development with App Router, Server Components, TypeScript

ABOUTME: Modern patterns for data fetching, state management, forms, testing, and styling

React 19 + Next.js 16

What's New (2025-2026)

React 19.2 Next.js 16 Tailwind v4
useActionState use cache directive CSS-first config
useFormStatus proxy.ts Oxide engine (100x faster)
useOptimistic Turbopack default Container queries
React Compiler DevTools MCP

Commands

npm run dev && npm run build && npm run test && npm run typecheck

Core Patterns

// Server Component (default)
async function Page() {
  const data = await fetchData()
  return <Component data={data} />
}

// Client Component
'use client'
function Interactive() {
  const [state, setState] = useState()
  return <button onClick={() => setState(x => x + 1)} />
}

// Server Action
async function submit(formData: FormData) {
  'use server'
  await db.insert(formData)
}

Project Structure

src/
├── app/                    # App Router
├── components/ui/          # Primitives
├── features/*/             # Feature modules
├── lib/                    # Utils
├── stores/                 # Zustand
└── types/

Server vs Client Components

Default to Server. Client only when needed.

Server Client
Fetch data, DB access onClick, onChange
Sensitive data useState, useEffect
Large deps, SEO Browser APIs

Data Fetching

// Server
async function getPosts() {
  const res = await fetch('https://api.example.com/posts', { next: { revalidate: 60 } })
  return res.json()
}

// Next.js 16 Caching
async function getData() {
  'use cache'
  cacheLife('minutes')
  return fetchData()
}

// Client: TanStack Query
'use client'
export function usePosts() {
  return useQuery({ queryKey: ['posts'], queryFn: api.posts.list })
}

State Management

Library Use Case
TanStack Query Server state
Zustand Global client
nuqs URL state
// Zustand
export const useAuthStore = create<AuthState>()(
  persist((set) => ({
    user: null,
    login: (user) => set({ user }),
    logout: () => set({ user: null }),
  }), { name: 'auth-storage' })
)

Forms & Validation

// Server Action + useActionState
'use server'
const schema = z.object({ email: z.string().email(), password: z.string().min(8) })

export async function login(prev: State, formData: FormData): Promise<State> {
  const result = schema.safeParse(Object.fromEntries(formData))
  if (!result.success) return { errors: result.error.flatten().fieldErrors }
  await authenticate(result.data)
  redirect('/dashboard')
}

// Component
'use client'
const [state, action, isPending] = useActionState(login, { errors: {} })
return <form action={action}>...</form>

Testing

Type Tool
Unit Vitest + RTL
E2E Playwright
describe('Button', () => {
  it('calls onClick', () => {
    const onClick = vi.fn()
    render(<Button onClick={onClick}>Click</Button>)
    fireEvent.click(screen.getByRole('button'))
    expect(onClick).toHaveBeenCalled()
  })
})

Performance

<Image src={src} alt={alt} width={w} height={h} priority={isAboveFold} />
const Heavy = dynamic(() => import('@/components/heavy'), { loading: () => <Skeleton /> })
experimental: { reactCompiler: true }  // Auto-memoization

Checklist

  • No any, no unnecessary 'use client'
  • Server/Client correctly separated
  • Forms: useActionState + useFormStatus
  • useOptimistic for mutations
  • Images: next/image + priority

Libraries: TanStack Query, Zustand, nuqs, Zod, Vitest + Playwright