遇到 NEXT_REDIRECT 或 notFound() 错误如何排查?

51 阅读2分钟

遇到 NEXT_REDIRECT 或 notFound() 错误如何排查?

NEXT_REDIRECT 错误排查

1. 错误类型和原因

// 常见的 NEXT_REDIRECT 错误
// 1. 在客户端组件中使用 redirect()
'use client'
import { redirect } from 'next/navigation'

export default function ClientComponent() {
  // ❌ 错误:不能在客户端组件中使用 redirect()
  const handleClick = () => {
    redirect('/dashboard') // 会抛出 NEXT_REDIRECT 错误
  }

  return <button onClick={handleClick}>Go to Dashboard</button>
}

// 2. 在异步函数中错误使用
export default async function ServerComponent() {
  // ❌ 错误:在异步函数中直接使用 redirect()
  const data = await fetchData()
  if (!data) {
    redirect('/404') // 可能抛出 NEXT_REDIRECT 错误
  }

  return <div>{data}</div>
}

2. 正确的重定向方式

// ✅ 正确:在服务器组件中使用 redirect()
// app/login/page.js
import { redirect } from 'next/navigation'
import { cookies } from 'next/headers'

export default async function LoginPage() {
  const cookieStore = cookies()
  const token = cookieStore.get('auth-token')

  // 如果已登录,重定向到仪表盘
  if (token) {
    redirect('/dashboard')
  }

  return (
    <div>
      <h1>Login</h1>
      {/* 登录表单 */}
    </div>
  )
}

// ✅ 正确:在客户端组件中使用 useRouter
'use client'
import { useRouter } from 'next/navigation'

export default function ClientComponent() {
  const router = useRouter()

  const handleClick = () => {
    router.push('/dashboard') // 正确的客户端重定向
  }

  return <button onClick={handleClick}>Go to Dashboard</button>
}

3. 错误处理

// app/api/protected/route.js
import { NextResponse } from 'next/server'
import { redirect } from 'next/navigation'

export async function GET(request) {
  try {
    const token = request.headers.get('authorization')

    if (!token) {
      // ✅ 正确:在 API 路由中使用 NextResponse.redirect
      return NextResponse.redirect(new URL('/login', request.url))
    }

    // 处理请求
    return NextResponse.json({ message: 'Success' })
  } catch (error) {
    console.error('API Error:', error)
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    )
  }
}

notFound() 错误排查

1. 错误类型和原因

// 常见的 notFound() 错误
// 1. 在客户端组件中使用 notFound()
'use client'
import { notFound } from 'next/navigation'

export default function ClientComponent() {
  // ❌ 错误:不能在客户端组件中使用 notFound()
  const handleError = () => {
    notFound() // 会抛出错误
  }

  return <button onClick={handleError}>Trigger 404</button>
}

// 2. 在异步函数中错误使用
export default async function ServerComponent() {
  // ❌ 错误:在异步函数中直接使用 notFound()
  const data = await fetchData()
  if (!data) {
    notFound() // 可能抛出错误
  }

  return <div>{data}</div>
}

2. 正确的 404 处理方式

// ✅ 正确:在服务器组件中使用 notFound()
// app/blog/[slug]/page.js
import { notFound } from 'next/navigation'

export default async function BlogPost({ params }) {
  const { slug } = params

  try {
    const post = await fetch(`https://api.example.com/posts/${slug}`)

    if (!post.ok) {
      notFound() // 正确的 404 处理
    }

    const data = await post.json()

    return (
      <article>
        <h1>{data.title}</h1>
        <div dangerouslySetInnerHTML={{ __html: data.content }} />
      </article>
    )
  } catch (error) {
    console.error('Error fetching post:', error)
    notFound()
  }
}

// ✅ 正确:在客户端组件中使用条件渲染
'use client'
import { useState } from 'react'

export default function ClientComponent() {
  const [data, setData] = useState(null)
  const [error, setError] = useState(null)

  const fetchData = async () => {
    try {
      const response = await fetch('/api/data')
      if (!response.ok) {
        setError('Data not found')
        return
      }
      const result = await response.json()
      setData(result)
    } catch (err) {
      setError('Failed to fetch data')
    }
  }

  if (error) {
    return <div>Error: {error}</div>
  }

  if (!data) {
    return <div>Loading...</div>
  }

  return <div>{data}</div>
}

3. 自定义 404 页面

// app/not-found.js
export default function NotFound() {
  return (
    <div>
      <h1>404 - Page Not Found</h1>
      <p>The page you're looking for doesn't exist.</p>
      <a href="/">Go back home</a>
    </div>
  )
}

调试技巧

1. 错误日志

// 添加详细的错误日志
export default async function ServerComponent() {
  try {
    const data = await fetchData()

    if (!data) {
      console.error('Data not found for:', params)
      notFound()
    }

    return <div>{data}</div>
  } catch (error) {
    console.error('Error in ServerComponent:', error)
    throw error
  }
}

2. 错误边界

// app/error.js
'use client'
import { useEffect } from 'react'

export default function Error({ error, reset }) {
  useEffect(() => {
    console.error('Error:', error)
  }, [error])

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

3. 开发环境调试

// 开发环境下的调试信息
export default async function ServerComponent() {
  if (process.env.NODE_ENV === 'development') {
    console.log('ServerComponent rendered with params:', params)
  }

  const data = await fetchData()

  if (!data) {
    if (process.env.NODE_ENV === 'development') {
      console.error('Data not found, redirecting to 404')
    }
    notFound()
  }

  return <div>{data}</div>
}

常见问题和解决方案

1. 重定向循环

// ❌ 错误:可能导致重定向循环
export default async function Page() {
  const user = await getCurrentUser()

  if (!user) {
    redirect('/login')
  }

  if (user.role !== 'admin') {
    redirect('/unauthorized')
  }

  return <div>Admin content</div>
}

// ✅ 正确:避免重定向循环
export default async function Page() {
  const user = await getCurrentUser()

  if (!user) {
    redirect('/login')
  }

  if (user.role !== 'admin') {
    redirect('/unauthorized')
  }

  return <div>Admin content</div>
}

2. 异步操作中的错误处理

// ❌ 错误:在异步操作中直接使用 redirect/notFound
export default async function Page() {
  const data = await fetchData()

  if (!data) {
    notFound() // 可能不会正确工作
  }

  return <div>{data}</div>
}

// ✅ 正确:使用 try-catch 处理异步错误
export default async function Page() {
  try {
    const data = await fetchData()

    if (!data) {
      notFound()
    }

    return <div>{data}</div>
  } catch (error) {
    console.error('Error fetching data:', error)
    notFound()
  }
}

3. 客户端重定向

// ✅ 正确:客户端重定向
'use client'
import { useRouter } from 'next/navigation'
import { useEffect } from 'react'

export default function ClientComponent() {
  const router = useRouter()

  useEffect(() => {
    const checkAuth = async () => {
      const token = localStorage.getItem('auth-token')

      if (!token) {
        router.push('/login')
      }
    }

    checkAuth()
  }, [router])

  return <div>Protected content</div>
}

最佳实践

1. 错误处理策略

// 统一的错误处理
export async function handleServerError(error, context) {
  console.error(`Error in ${context}:`, error)

  if (error.status === 404) {
    notFound()
  }

  if (error.status === 401) {
    redirect('/login')
  }

  throw error
}

2. 类型安全

// 类型安全的错误处理
interface ApiResponse<T> {
  data: T | null
  error: string | null
}

export async function fetchData<T>(url: string): Promise<T> {
  try {
    const response = await fetch(url)

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`)
    }

    const result: ApiResponse<T> = await response.json()

    if (result.error) {
      throw new Error(result.error)
    }

    if (!result.data) {
      notFound()
    }

    return result.data
  } catch (error) {
    console.error('Error fetching data:', error)
    throw error
  }
}

总结

错误排查要点:

NEXT_REDIRECT 错误

  • 不能在客户端组件中使用 redirect()
  • 在 API 路由中使用 NextResponse.redirect
  • 在客户端使用 useRouter

notFound() 错误

  • 不能在客户端组件中使用 notFound()
  • 在服务器组件中正确使用
  • 创建自定义 404 页面

调试技巧

  • 添加错误日志
  • 使用错误边界
  • 开发环境调试

最佳实践

  • 避免重定向循环
  • 正确处理异步错误
  • 统一的错误处理策略