NextJS 预渲染时 Axios 转发元数据
背景
现在很多网站都使用了前后端的分离的架构,前后端可以不在一台服务器上,前端为了保证 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']
就能拿到。
以上就是完整的代码了。