定制 next.js 500页面,透出具体的接口错误

550 阅读2分钟

Q: 我们平常用next.js的时候,500页面除了改改样式,就不能做更多的定制化了,如果想显示接口抛出的具体错误code,方便用户反馈bug怎么办呢?

解决办法,直接上代码

const Error = ({ statusCode }: ErrorProps) => {
  // hack
  const [errCode, setErrCode] = useState('')
  useEffect(() => {
    try {
      const p = JSON.parse(
        document.getElementById('__NEXT_DATA__')?.textContent as string
      )
      setTraceid(p.props.pageProps.errCode)
    } catch (err) {}
  }, [])

  return (
    <>
      <h1>500</h1>
      <div>{errCode}</div>
    </>
  )
}

Error.getInitialProps = async ({
  res,
  err,
  pathname,
  query,
  AppTree,
}: NextPageContext) => {
  const errorInitialProps = await NextError.getInitialProps({
    res,
    err,
    pathname,
    query,
    AppTree,
  })

  let errCode = err.code
  return {
    ...errorInitialProps,
    errCode,
  }
}

export default Error

Q: 代码一看就懂,但是为什么要这样呢?

getInitialProps 返回的 errCode 是会丢失的。直接用props去接收errCode你就会发现页面上的errCode闪烁一下就消失了。

假设如果用props去接收errCode会发生什么?

next.js渲染是分为两部分的,首先是服务端渲染出html显示到页面上, 然后客户端再次渲染出html替换掉服务端渲染的html(这个过程叫做hydrate)。

首先服务端渲染出的结果是没有问题的,但是客户端hydrate的过程中渲染出来的结果是没有errCode的导致了上述现象的发生

Q: 为什么客户端渲染的时props.errCode丢失了

我的的errCode是从err.code取出来的

  • 服务端调用getInitialProps({err: err})时用的是代码里throw出的原始错误
  • 客户端调用getInitialProps({err: err})时用的错误是
{
    message: "500 - Internal Server Error."
    name: "Internal Server Error."
    statusCode :500
  }

所以客户端渲染时 errCode 返回的就是undefined,然后就从页面消失了。

Q: 为什么这个错误变掉了?

直接上next.js源码,nextjs在序列化error给客户端用时调用了以下函数

function serializeError(
  dev: boolean | undefined,
  err: Error
): Error & {
  statusCode?: number
  source?: typeof COMPILER_NAMES.server | typeof COMPILER_NAMES.edgeServer
} {
  if (dev) {
    return errorToJSON(err)
  }

  return {
    name: 'Internal Server Error.',
    message: '500 - Internal Server Error.',
    statusCode: 500,
  }
}

这里原始错误就消失了


到此已经全部明了,我们只要拿到next.js服务端返回的原始数据就可以解决最开始的问题了。

next.js服务端返回的原始数据怎么拿呢

const p = JSON.parse(
        document.getElementById('__NEXT_DATA__')?.textContent as string
      )

或者你直接用

window.__NEXT_DATA__

也行

嘿嘿!