在 NextJS 预获取数据时正确处理异常请求
在用 NextJS 构建 SSR 应用的时候总是要在页面的 getInitialProps
方法中预先获取数据,然后由框架注水,以便在 hydate 时候不用再由浏览器端去加载重复数据。但是在这一个步骤中请求可能会发生错误。如果请求的接口应该是由服务端给出的 404,但是在这个步骤中请求抛出错误之后,NextJS 会直接捕获并给错误页,状态却是 500,这明显不是我们预期的结果,期望得到的是请求状态 404。由于 NextJS 不知道如何处理异常,所以默认状态码是 500,那么如何解决,转发出原请求的状态码呢。
首先模拟一个 404 请求。以下由 express 为例。
const router = express.Router()
router.get('/data', (req, res) => {
res.status(200).send({ data: 'Hello World!' })
})
router.get('/404', (req, res) => {
res.status(404).send({ message: '页面走丢了' })
})
router.get('/403', (req, res) => {
res.status(403).send({ message: '这..不能看!' })
})
server.use('/mock', router)
在 /mock
路由下挂载 3 个路由,分别是 mock 404,403,200 的路由。新建一个 NextPage,去获取 404 的数据。如下:
// pages/get-404.js
import { $axios } from '../utils/axios'
const _404 = (props) => {
return <pre>{props.data}</pre>
}
_404.getInitialProps = async () => {
const { data } = await $axios.get('/mock/404')
return data
}
export default _404
在开发模式下,NextJS 会直接把异常抛出而不会进入 Error 页,所以需要先 build 然后在生产环境下启动 NextJS。在生产环境下访问这个路由会出现如下页面。
Error 页显示 500,请求 /get-404
也是 500,而我们想要的是 404。
根据文档所说,我们可以新建 _Error.js
去处理异常,官网给的案例如下:
// pages/_error.js
import ErrorPage from 'next/error'
ErrorPage.getInitialProps = async ({ res, err }) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404
return { statusCode }
}
export default ErrorPage
这样并不能很好的解决问题。接下来我们来修改一下 axios 的异常拦截器。
const axios = require('axios')
const $axios = axios.default.create({
baseURL: 'http://127.0.0.1:3000',
})
$axios.interceptors.response.use(undefined, (err) => {
const response = err.response
const status = response ? response.status : 408
return Promise.reject(new RequestError(status, response))
})
export { $axios }
class RequestError extends Error {
constructor(status, response) {
const message = response
? response.data?.message || 'Unknown Error'
: 'Request timeout'
super(message)
this.status = status
this.response = response
}
}
在拦截器中把异常进行处理,并由自定义异常类再次抛出。在这个类中,我们可以预先获取到 HTTP 异常码,注意:如果是网络中断问题,status
为 ECONNREFUSED
,需要在额外判断。
然后在 _error.js
中处理。由于默认的 ErrorPage 不支持自定义所以稍作修改 UI。展示后端异常中的 message
。
// @ts-check
import { RequestError } from '../utils/axios'
import styles from './error.module.css'
const MyErrorPage = ({ message, statusCode }) => {
return (
<div className={styles['error']}>
<div>
<h1>{statusCode}</h1>
<div className={styles['desc']}>{message}</div>
</div>
</div>
)
}
MyErrorPage.getInitialProps = async ({ res, err }) => {
if (err instanceof RequestError) {
const { status, message } = err
res && (res.statusCode = typeof status === 'number' ? status : 408)
return {
statusCode: status,
message,
}
}
return { statusCode: res.statusCode, message: '' }
}
export default MyErrorPage
在 ErrorPage 的 getInitialProps
中处理异常,如果是预先定义的 RequestError
的话,就是请求异常了,直接获取到异常中的 message,statuscode 字段直接返回就好了。同时可以修改 res.statusCode
覆盖原有的 500 状态码,从而达到转发请求异常状态码。重新 build 后,如下图。
已实现预期,同时获取页面时状态码也是 404。好耶。403 也是可以的。