debug:nextjs

📁 snakeo/claude-debug-and-refactor-skills-plugin 📅 Jan 19, 2026
17
总安装量
15
周安装量
#20442
全站排名
安装命令
npx skills add https://github.com/snakeo/claude-debug-and-refactor-skills-plugin --skill debug:nextjs

Agent 安装分布

claude-code 14
opencode 12
gemini-cli 11
github-copilot 10
codex 10

Skill 文档

Next.js Debugging Guide

This guide provides a systematic approach to debugging Next.js applications, covering common error patterns, debugging tools, and resolution strategies for both development and production environments.

Common Error Patterns

1. Hydration Mismatches

Symptoms:

Warning: Text content did not match. Server: '...' Client: '...'
Warning: Expected server HTML to contain a matching <div> in <div>
Hydration failed because the initial UI does not match what was rendered on the server

Common Causes:

  • Using Date.now(), Math.random(), or timestamps in render
  • Browser-only APIs accessed during SSR (window, localStorage, document)
  • Conditional rendering based on client-only state
  • Extension-injected HTML elements
  • Invalid HTML nesting (e.g., <p> inside <p>, <div> inside <p>)

Solutions:

// BAD: Causes hydration mismatch
function Component() {
  return <p>Current time: {new Date().toLocaleString()}</p>
}

// GOOD: Use useEffect for client-only values
'use client'
import { useState, useEffect } from 'react'

function Component() {
  const [time, setTime] = useState<string>('')

  useEffect(() => {
    setTime(new Date().toLocaleString())
  }, [])

  return <p>Current time: {time || 'Loading...'}</p>
}

// GOOD: Suppress hydration warning for intentional mismatches
<time suppressHydrationWarning>
  {new Date().toLocaleString()}
</time>

2. Server/Client Component Confusion

Symptoms:

Error: useState only works in Client Components. Add the "use client" directive
Error: You're importing a component that needs useEffect. It only works in a Client Component
Error: createContext only works in Client Components

Understanding the Boundary:

// Server Component (default in App Router)
// - Can't use hooks (useState, useEffect, etc.)
// - Can't use browser APIs
// - CAN use async/await directly
// - CAN access backend resources directly

// app/page.tsx (Server Component)
async function Page() {
  const data = await db.query('SELECT * FROM posts') // Direct DB access
  return <PostList posts={data} />
}

// Client Component
// - CAN use hooks
// - CAN use browser APIs
// - Must be marked with 'use client'

// components/Counter.tsx
'use client'
import { useState } from 'react'

export function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}

Context Provider Pattern:

// providers/theme-provider.tsx
'use client'
import { createContext, useContext, useState } from 'react'

const ThemeContext = createContext<{ theme: string } | null>(null)

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState('light')
  return (
    <ThemeContext.Provider value={{ theme }}>
      {children}
    </ThemeContext.Provider>
  )
}

// app/layout.tsx (Server Component that uses Client Provider)
import { ThemeProvider } from '@/providers/theme-provider'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  )
}

3. Dynamic Import Issues

Symptoms:

Error: Element type is invalid. Received undefined
Module not found errors in production
Components rendering as null

Solutions:

// Dynamic import with SSR disabled (for browser-only libraries)
import dynamic from 'next/dynamic'

const MapComponent = dynamic(() => import('@/components/Map'), {
  ssr: false,
  loading: () => <p>Loading map...</p>
})

// Dynamic import with named exports
const Modal = dynamic(() =>
  import('@/components/Modal').then(mod => mod.Modal)
)

// Dynamic import with error handling
const Chart = dynamic(
  () => import('@/components/Chart').catch(err => {
    console.error('Failed to load Chart:', err)
    return () => <div>Failed to load chart</div>
  }),
  { ssr: false }
)

4. API Route Errors

App Router (Route Handlers):

// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server'

// GET is cached by default - use dynamic to opt out
export const dynamic = 'force-dynamic'

export async function GET(request: NextRequest) {
  try {
    const searchParams = request.nextUrl.searchParams
    const id = searchParams.get('id')

    const data = await fetchUser(id)
    return NextResponse.json(data)
  } catch (error) {
    console.error('API Error:', error)
    return NextResponse.json(
      { error: 'Internal Server Error' },
      { status: 500 }
    )
  }
}

export async function POST(request: NextRequest) {
  try {
    const body = await request.json()
    // Validate body...
    const result = await createUser(body)
    return NextResponse.json(result, { status: 201 })
  } catch (error) {
    if (error instanceof SyntaxError) {
      return NextResponse.json(
        { error: 'Invalid JSON' },
        { status: 400 }
      )
    }
    throw error
  }
}

Common API Route Issues:

// ISSUE: Route Handler cached unexpectedly
// SOLUTION: Force dynamic rendering
export const dynamic = 'force-dynamic'
export const revalidate = 0

// ISSUE: Can't access cookies/headers
// SOLUTION: Import from next/headers
import { cookies, headers } from 'next/headers'

export async function GET() {
  const cookieStore = await cookies()
  const token = cookieStore.get('token')

  const headersList = await headers()
  const userAgent = headersList.get('user-agent')
}

// ISSUE: CORS errors
// SOLUTION: Add CORS headers
export async function GET() {
  const response = NextResponse.json({ data: 'test' })
  response.headers.set('Access-Control-Allow-Origin', '*')
  response.headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
  return response
}

export async function OPTIONS() {
  return new NextResponse(null, {
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type',
    },
  })
}

5. Build Failures

Common Build Errors:

# Type errors during build
Error: Type error: Property 'x' does not exist on type 'y'
# Solution: Fix TypeScript errors or use type assertions

# Module resolution failures
Error: Module not found: Can't resolve '@/components/...'
# Solution: Check tsconfig.json paths and next.config.js

# Static generation failures
Error: getStaticPaths is required for dynamic SSG pages
# Solution: Add getStaticPaths or use generateStaticParams

# Image optimization errors
Error: Invalid src prop on next/image
# Solution: Configure domains in next.config.js

next.config.js Debugging:

/** @type {import('next').NextConfig} */
const nextConfig = {
  // Enable verbose build output
  logging: {
    fetches: {
      fullUrl: true,
    },
  },

  // Debug image domains
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: '**.example.com',
      },
    ],
  },

  // Analyze bundle size
  webpack: (config, { isServer }) => {
    if (process.env.ANALYZE) {
      const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
      config.plugins.push(
        new BundleAnalyzerPlugin({
          analyzerMode: 'static',
          reportFilename: isServer
            ? '../analyze/server.html'
            : './analyze/client.html',
        })
      )
    }
    return config
  },
}

module.exports = nextConfig

6. Middleware Issues

Symptoms:

Middleware triggered unexpectedly
Infinite redirect loops
Middleware not executing
Edge runtime compatibility errors

Debugging Middleware:

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  // Debug: Log all middleware invocations
  console.log('Middleware:', request.method, request.nextUrl.pathname)

  // Check if this path should be handled
  const pathname = request.nextUrl.pathname

  // Skip static files and API routes if needed
  if (
    pathname.startsWith('/_next') ||
    pathname.startsWith('/api') ||
    pathname.includes('.')
  ) {
    return NextResponse.next()
  }

  // Example: Auth check
  const token = request.cookies.get('token')?.value

  if (!token && pathname.startsWith('/dashboard')) {
    const url = new URL('/login', request.url)
    url.searchParams.set('from', pathname)
    return NextResponse.redirect(url)
  }

  return NextResponse.next()
}

// Limit middleware to specific paths
export const config = {
  matcher: [
    // Match all paths except static files
    '/((?!_next/static|_next/image|favicon.ico).*)',
  ],
}

7. Caching and Revalidation Issues

Symptoms:

Stale data after updates
Pages not revalidating
Unexpected cache hits/misses

Solutions:

// Force dynamic rendering (no cache)
export const dynamic = 'force-dynamic'
export const revalidate = 0

// Time-based revalidation
export const revalidate = 60 // Revalidate every 60 seconds

// On-demand revalidation
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache'
import { NextRequest } from 'next/server'

export async function POST(request: NextRequest) {
  const { path, tag, secret } = await request.json()

  if (secret !== process.env.REVALIDATION_SECRET) {
    return Response.json({ error: 'Invalid secret' }, { status: 401 })
  }

  if (path) {
    revalidatePath(path)
  }
  if (tag) {
    revalidateTag(tag)
  }

  return Response.json({ revalidated: true, now: Date.now() })
}

// Using fetch with cache tags
async function getPosts() {
  const res = await fetch('https://api.example.com/posts', {
    next: { tags: ['posts'], revalidate: 3600 }
  })
  return res.json()
}

Debugging Tools

1. VS Code Debugger Setup

Create .vscode/launch.json:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Next.js: debug server-side",
      "type": "node-terminal",
      "request": "launch",
      "command": "npm run dev"
    },
    {
      "name": "Next.js: debug client-side",
      "type": "chrome",
      "request": "launch",
      "url": "http://localhost:3000"
    },
    {
      "name": "Next.js: debug full stack",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/node_modules/.bin/next",
      "args": ["dev"],
      "skipFiles": ["<node_internals>/**"],
      "serverReadyAction": {
        "action": "debugWithChrome",
        "pattern": "- Local:.+(https?://.+)",
        "uriFormat": "%s",
        "webRoot": "${workspaceFolder}"
      }
    }
  ]
}

2. Next.js 16.1+ Debug Flag

# Enable Node.js debugger directly
next dev --inspect

# With specific port
next dev --inspect=9229

# Break on first line
next dev --inspect-brk

3. Chrome DevTools

# Start with debugging
NODE_OPTIONS='--inspect' npm run dev

# Then open in Chrome:
# chrome://inspect

4. React DevTools

  • Install browser extension
  • Use Components tab to inspect component hierarchy
  • Use Profiler tab to identify re-render issues
  • Enable “Highlight updates when components render”

5. Next.js Error Overlay

The development error overlay provides:

  • Full stack traces with source maps
  • Component stack for React errors
  • Quick links to open files in editor

Configure editor integration in next.config.js:

module.exports = {
  experimental: {
    // Open files in VS Code
    serverActions: {
      bodySizeLimit: '2mb',
    },
  },
}

The Four Phases of Next.js Debugging

Phase 1: Reproduce and Isolate

Steps:

  1. Reproduce the issue consistently
  2. Determine if it’s client-side, server-side, or build-time
  3. Check if it occurs in development, production, or both
  4. Isolate to a minimal reproduction

Commands:

# Test in development
npm run dev

# Test production build locally
npm run build && npm run start

# Check for TypeScript errors
npx tsc --noEmit

# Check for ESLint errors
npm run lint

Phase 2: Gather Information

Client-Side:

// Add debug logging
'use client'
import { useEffect } from 'react'

export function DebugComponent({ data }: { data: unknown }) {
  useEffect(() => {
    console.log('Component mounted with data:', data)
    console.log('Window location:', window.location.href)
    console.log('User agent:', navigator.userAgent)
  }, [data])

  return null
}

Server-Side:

// app/page.tsx
export default async function Page() {
  console.log('Server rendering page')
  console.log('Environment:', process.env.NODE_ENV)
  console.log('Request time:', new Date().toISOString())

  const data = await fetchData()
  console.log('Fetched data:', JSON.stringify(data, null, 2))

  return <div>...</div>
}

Request/Response Debugging:

// middleware.ts
export function middleware(request: NextRequest) {
  console.log('=== Request Debug ===')
  console.log('URL:', request.url)
  console.log('Method:', request.method)
  console.log('Headers:', Object.fromEntries(request.headers))
  console.log('Cookies:', request.cookies.getAll())

  const response = NextResponse.next()

  // Add debug header
  response.headers.set('X-Debug-Time', Date.now().toString())

  return response
}

Phase 3: Analyze and Diagnose

Check Build Output:

# Analyze bundle
ANALYZE=true npm run build

# Check static/dynamic pages
npm run build
# Look for:
# - Static (SSG) pages: filled circle
# - Server-rendered (SSR): lambda
# - ISR pages: filled circle with revalidation time

Check Network Requests:

// Wrap fetch for debugging
const originalFetch = global.fetch
global.fetch = async (...args) => {
  const [url, options] = args
  console.log('Fetch:', url, options?.method || 'GET')

  const start = Date.now()
  const response = await originalFetch(...args)
  const duration = Date.now() - start

  console.log('Response:', response.status, `${duration}ms`)
  return response
}

Phase 4: Fix and Verify

Common Fix Patterns:

// Fix 1: Add error boundary
// app/error.tsx
'use client'

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  console.error('Error boundary caught:', error)

  return (
    <div>
      <h2>Something went wrong!</h2>
      <p>{error.message}</p>
      <button onClick={reset}>Try again</button>
    </div>
  )
}

// Fix 2: Add loading state
// app/loading.tsx
export default function Loading() {
  return <div>Loading...</div>
}

// Fix 3: Handle async errors properly
async function getData() {
  try {
    const res = await fetch('...')
    if (!res.ok) {
      throw new Error(`HTTP ${res.status}: ${res.statusText}`)
    }
    return res.json()
  } catch (error) {
    console.error('getData failed:', error)
    // Return fallback or re-throw
    return { error: true, data: null }
  }
}

Quick Reference Commands

Development

# Start development server
npm run dev
next dev

# Start with debugging
next dev --inspect
NODE_OPTIONS='--inspect' npm run dev

# Start with Turbopack (faster HMR)
next dev --turbopack

# Clear Next.js cache
rm -rf .next

Building

# Production build
npm run build
next build

# Build with verbose output
NEXT_DEBUG=1 npm run build

# Analyze bundle size
ANALYZE=true npm run build

Linting and Type Checking

# Run ESLint
npm run lint
next lint

# Fix ESLint errors
next lint --fix

# Type check
npx tsc --noEmit

# Type check with watch mode
npx tsc --noEmit --watch

Production Testing

# Start production server
npm run start
next start

# Start on specific port
next start -p 3001

# Test production build locally
npm run build && npm run start

Cache Management

# Clear all caches
rm -rf .next node_modules/.cache

# Clear just build cache
rm -rf .next

# Clear fetch cache (App Router)
# Add to fetch: { cache: 'no-store' }
# Or: export const revalidate = 0

Debugging Specific Issues

# Check for circular dependencies
npx madge --circular --extensions ts,tsx ./app

# Find large dependencies
npx source-map-explorer .next/static/chunks/*.js

# Check bundle composition
npx @next/bundle-analyzer

Environment-Specific Debugging

Development vs Production Differences

// Check environment
const isDev = process.env.NODE_ENV === 'development'
const isProd = process.env.NODE_ENV === 'production'

// Development-only logging
if (isDev) {
  console.log('Debug info:', data)
}

// Production error reporting
if (isProd && error) {
  // Send to error tracking service
  Sentry.captureException(error)
}

Environment Variables

# Check env vars are loaded
console.log('Env check:', {
  NODE_ENV: process.env.NODE_ENV,
  API_URL: process.env.NEXT_PUBLIC_API_URL,
  // Note: Only NEXT_PUBLIC_* vars are available client-side
})

Performance Debugging

Web Vitals Monitoring

// app/layout.tsx
import { SpeedInsights } from '@vercel/speed-insights/next'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <SpeedInsights />
      </body>
    </html>
  )
}

// Custom Web Vitals reporting
// pages/_app.tsx (Pages Router)
export function reportWebVitals(metric) {
  console.log(metric.name, metric.value)
  // Send to analytics
}

React Profiler

import { Profiler } from 'react'

function onRenderCallback(
  id,
  phase,
  actualDuration,
  baseDuration,
  startTime,
  commitTime
) {
  console.log(`${id} ${phase}: ${actualDuration.toFixed(2)}ms`)
}

export default function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <MainComponent />
    </Profiler>
  )
}

Sources and References

This guide was compiled using information from: