背景

现在很多网站都使用了前后端的分离的架构,前后端可以不在一台服务器上,前端为了保证 SEO,必须使用预渲染,SSG 或 SSR 技术。而我的站点则使用了 NextJS 的 SSR 技术。在渲染端预渲染页面时首先会调用 Axios 实例去请求接口。但是有一个问题。在渲染端请求的头部永远是渲染端本身的 User-Agent 和 IP,并不能获取到用户本身的元数据。显然这并不是我们先要期望得到的结果。当然这个情况只发生在首次访问。
为了解决这种问题,必须想办法把原本的请求头部或者其他元数据转发到此次请求上。有点类似反向代理,但是又有点不同。好在 NextJS 为我们提供了这一接口。

踩坑之路

带着这个想法,我踩了很多坑。
首先我查到 NextJS 可以在 Custom App 上定义 getInitialProps (和 NextPage 一致)。但是它接受一个参数,类型为 AppContext 位于 next/app 包中。
getInitialProps 必须返回一个对象,但是因为他是 Root Component。必须接受所有 Children props,然后返回。好在 NextJS 为我们提供了一个方法,我们只需要如下操作就能完成建基。
app.getInitialProps = async (props: AppContext) => {
   const appProps = await App.getInitialProps(props);
	 return { ...appProps }
}
之后我们需要进行扩展。
首先我们要知道 props 上有一个 ctx 的对象,ctx 中有一个 req 对象,类型为 IncomingMessage。这个 req 对象就是用户的请求,我们只需要把这个 req中的某些元数据附加到之后请求的 axios 实例上即可。当然只需要判断是不是在预渲染的时候就行了,因为如果不在渲染端就不需要做转发。
我们可以使用 typeof window === 'undefined' 来判断是否在渲染端。
export const isClientSide = () => {
  return typeof window !== 'undefined'
}
export const isServerSide = () => {
  return !isClientSide()
}
之后就是怎么获取到用户的真实 IP 了,如果使用了 Nginx 或者其他服务器软件进行反代,一般会把真实 IP 附加到 Headers 上。
const getRealIP = (request: IncomingMessage) => {
   let ip =
      ((request.headers['x-forwarded-for'] ||
        request.connection.remoteAddress ||
        request.socket.remoteAddress) as string) || undefined
    if (ip && ip.split(',').length > 0) {
      ip = ip.split(',')[0]
    }
	 return ip
}
之后就是怎么附加到 Axios 上。这里有一个坑,不要直接附加到 Axios.default.headers 上,因为这样看似可以(的确只在 dev 环境可以),但是 production 立马暴毙,血的教训
我们可以附加到 Axios 实例上。直接在 service.default.headers.common 上对键进行赋值即可。
import Package from 'package.json'
import service from '../utils/request'
import { isServerSide } from '../utils'
import App, { AppContext } from 'next/app'

app.getInitialProps = async (props: AppContext) => {
  const appProps = await App.getInitialProps(props)

  const ctx = props.ctx
  const request = ctx.req

  if (request && isServerSide()) {
    let ip =
      ((request.headers['x-forwarded-for'] ||
        request.connection.remoteAddress ||
        request.socket.remoteAddress) as string) || undefined
    if (ip && ip.split(',').length > 0) {
      ip = ip.split(',')[0]
    }
    service.defaults.headers.common['x-forwarded-for'] = ip

    service.defaults.headers.common['User-Agent'] =
      request.headers['user-agent'] +
      ' mx-space render server' +
      `/v${Package.version}`
  }

  return { ...appProps }
}
而获取 UA 就简单了,直接 request.headers['user-agent'] 就能拿到。
以上就是完整的代码了。