building-nextjs-apps

mjunaidca's avatarfrom mjunaidca

Build Next.js 16 applications with correct patterns and distinctive design. Use when creatingpages, layouts, dynamic routes, upgrading from Next.js 15, or implementing proxy.ts. Coversbreaking changes (async params/searchParams, Turbopack, cacheComponents) and frontend aesthetics.NOT when building non-React or backend-only applications.

⭐1stars🔀2forks📁View on GitHub🕐Updated Dec 22, 2025

When & Why to Use This Skill

This Claude skill is a comprehensive guide and toolkit for building modern web applications using Next.js 16. It focuses on implementing the latest App Router patterns, handling critical breaking changes like asynchronous params, and integrating advanced frontend aesthetics with shadcn/ui. By combining best-practice code snippets with the Next.js DevTools MCP, it enables developers to automate upgrades, debug runtime issues, and maintain high-performance, type-safe React applications.

Use Cases

  • Migrating and upgrading existing Next.js 15 projects to Next.js 16, specifically refactoring synchronous params and searchParams into the required Promise-based patterns.
  • Bootstrapping new full-stack applications with a standardized architecture, including Tailwind CSS, shadcn/ui, and optimized project structures.
  • Implementing complex server-side logic and data mutations using Server Actions and the new proxy.ts middleware replacement for authentication and request routing.
  • Utilizing the Next.js DevTools MCP to perform real-time diagnostics, fetch official documentation, and execute automated browser testing via Playwright.
  • Developing high-performance UI components that leverage Next.js 16's Cache Components and the use() hook for efficient client-side data handling.
namebuilding-nextjs-apps
description|

Next.js 16 Applications

Build Next.js 16 applications correctly with distinctive design.

Critical Breaking Changes (Next.js 16)

1. params and searchParams are Now Promises

THIS IS THE MOST COMMON MISTAKE.

// WRONG - Next.js 15 pattern (WILL FAIL)
export default function Page({ params }: { params: { id: string } }) {
  return <div>ID: {params.id}</div>
}

// CORRECT - Next.js 16 pattern
export default async function Page({
  params,
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params
  return <div>ID: {id}</div>
}

2. Client Components Need use() Hook

"use client"
import { use } from "react"

export default function ClientPage({
  params,
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = use(params)
  return <div>ID: {id}</div>
}

3. searchParams Also Async

export default async function Page({
  searchParams,
}: {
  searchParams: Promise<{ page?: string }>
}) {
  const { page } = await searchParams
  return <div>Page: {page ?? "1"}</div>
}

Core Patterns

Project Setup

npx create-next-app@latest my-app --typescript --tailwind --eslint
cd my-app

# Add shadcn/ui
npx shadcn@latest init
npx shadcn@latest add button form dialog table sidebar

App Router Layout

// app/layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className="min-h-screen">
        {children}
      </body>
    </html>
  )
}

Dynamic Routes

// app/tasks/[id]/page.tsx
export default async function TaskPage({
  params,
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params
  const task = await getTask(id)

  return (
    <main>
      <h1>{task.title}</h1>
    </main>
  )
}

Server Actions

// app/actions.ts
"use server"

import { revalidatePath } from "next/cache"

export async function createTask(formData: FormData) {
  const title = formData.get("title") as string

  await db.insert(tasks).values({ title })

  revalidatePath("/tasks")
}

// Usage in component
<form action={createTask}>
  <input name="title" />
  <button type="submit">Create</button>
</form>

API Routes

// app/api/tasks/route.ts
import { NextResponse } from "next/server"

export async function GET() {
  const tasks = await db.select().from(tasksTable)
  return NextResponse.json(tasks)
}

export async function POST(request: Request) {
  const body = await request.json()
  const task = await db.insert(tasksTable).values(body).returning()
  return NextResponse.json(task, { status: 201 })
}

Middleware → proxy.ts

// proxy.ts (replaces middleware.ts in Next.js 16)
import { NextRequest } from "next/server"

export function proxy(request: NextRequest) {
  // Authentication check
  const token = request.cookies.get("token")
  if (!token && request.nextUrl.pathname.startsWith("/dashboard")) {
    return Response.redirect(new URL("/login", request.url))
  }
}

export const config = {
  matcher: ["/dashboard/:path*"],
}

Data Fetching

Server Component (Default)

// This runs on the server - can use async/await directly
async function TaskList() {
  const tasks = await fetch("https://api.example.com/tasks", {
    cache: "no-store", // SSR, or
    // next: { revalidate: 60 } // ISR
  }).then(r => r.json())

  return (
    <ul>
      {tasks.map(task => <li key={task.id}>{task.title}</li>)}
    </ul>
  )
}

Client Component

"use client"

import useSWR from "swr"

const fetcher = (url: string) => fetch(url).then(r => r.json())

export function ClientTaskList() {
  const { data, error, isLoading } = useSWR("/api/tasks", fetcher)

  if (isLoading) return <div>Loading...</div>
  if (error) return <div>Error loading tasks</div>

  return (
    <ul>
      {data.map(task => <li key={task.id}>{task.title}</li>)}
    </ul>
  )
}

Project Structure

app/
├── layout.tsx           # Root layout
├── page.tsx             # Home page
├── globals.css          # Global styles
├── api/                 # API routes
│   └── tasks/route.ts
├── tasks/
│   ├── page.tsx         # /tasks
│   └── [id]/page.tsx    # /tasks/:id
├── actions.ts           # Server actions
└── proxy.ts             # Request proxy (middleware)
components/
├── ui/                  # shadcn/ui components
└── task-list.tsx        # App components
lib/
├── db.ts                # Database connection
└── utils.ts             # Utilities

Next.js DevTools MCP

Use the next-devtools-mcp server for runtime diagnostics and development automation.

Setup

claude mcp add next-devtools npx next-devtools-mcp@latest

Or in settings.json:

{
  "mcpServers": {
    "next-devtools": {
      "type": "stdio",
      "command": "npx",
      "args": ["next-devtools-mcp@latest"]
    }
  }
}

Available Tools

Tool Purpose
init Establish context with available tools and best practices
nextjs_docs Search and fetch official Next.js documentation
browser_eval Automate browser testing with Playwright
nextjs_index Discover running Next.js dev servers
nextjs_call Execute MCP tools on running dev servers
upgrade_nextjs_16 Automated upgrade with codemods
enable_cache_components Configure Cache Components for Next.js 16

Key Use Cases

1. Get Real-time Errors

"What build errors are there in my Next.js app?"
"Show me TypeScript errors in the current project"

2. Debug Runtime Issues

"Check the dev server logs for errors"
"What runtime errors are happening on the dashboard page?"

3. Upgrade Assistance

"Upgrade this project to Next.js 16"
"Enable cache components for this app"

4. Documentation Lookup

"How do I use the Image component in Next.js 16?"
"What's the correct way to handle dynamic routes?"

Next.js 16 MCP Endpoint

Next.js 16+ exposes a built-in MCP endpoint at http://localhost:3000/_next/mcp (or your dev server port). The devtools MCP automatically discovers and connects to running servers.


Verification

Run: python3 scripts/verify.py

Expected: ✓ building-nextjs-apps skill ready

If Verification Fails

  1. Check: references/ folder has nextjs-16-patterns.md
  2. Stop and report if still failing
  • styling-with-shadcn - UI components for Next.js apps
  • fetching-library-docs - Latest Next.js docs: --library-id /vercel/next.js --topic routing
  • configuring-better-auth - OAuth/SSO for Next.js apps

References