Next.js: Data Fetching

 20th August 2020 at 2:19pm

对于一个单页应用,往往会做好前后端分离,后端只负责提供 API 以获取数据,而不参与 HTML 生成。Next.js 作为前后端的中间层,它提倡预生成(pre-render)页面,以获得更好的性能和 SEO 效果。这就意味着,一个使用后端 API 提供数据、可以展示不同数据的网站,可能有这几种方式实现:

  • 客户端拉取数据:即浏览器加载好页面框架,再发起异步请求拉取数据填充进来
  • 服务端预生成好整个页面。考虑到 SPA 的本质是 不刷新页面 以获得更好的性能和交互性,一般是在第一次打开网站时加载服务端预生成好的页面,后续的请求则由客户端来获得数据

客户端方式一般通过组件的 lifecycle event 来做,比如在 componentDidMount 中拉取数据。但是注意 componentDidMount 只会在组件初始化时被执行,如果初始化后还有操作引起数据变化,则需要再次拉取。

对于服务端方式,Next.js 提供了两种方式(官网文档):

  • Static Generation,即静态的内容生成,适用于类似产品官网或者个人博客这类内容网站。特点是不管页面模板还是内容都是预先生成好的,不会在用户访问时再度生成,优点是可以被 CDN 缓存以获得非常快的性能,缺点是无法生成动态内容(比如跟具体用户相关的内容)
  • Server-side Rendering,即页面是在每次用户访问时生成的。这种方式性能稍差,但是动态性好

Next.js 的 官网文档 详细描述了具体机制。注意 Next.js 也不限制你使用服务端方式的同时也使用客户端方式。

具体场景下选择怎样的方式?

如果你想做个静态生成内容的站点,那无疑使用 Next.js 的 static generation 即可。

对于动态内容的站点,从内容量的维度:

  • 如果页面中全部或者绝大多数内容都是动态的,建议使用 Next.js 的 server-side rendering
  • 如果页面中仅有部分内容是动态的,且这部分内容并不是重点,那建议使用客户端方式

从技术角度:

  • 如果你不愿意被限定在 Next.js 的 getServerSideProps 机制内,那可以考虑用客户端方式;但使用 Next.js 往往是为了用它提供的这些便利性

另外,考虑这样一个场景:你有一个显示图书简介的页面,它对应一个 Next.js 的 page。如果你想从显示第一本书变为显示第二本书,无论使用的是 <Link> 还是 Router.push() 时,只要 page 不变,page component 就不会被销毁重建。这意味着如果你用客户端方式来获取图书数据,你需要:

  • 维护加载数据的状态(state),比如在 componentDidMount 中加载第一次的数据,在跳转到第二本书的事件处理函数中加载第二次的数据
  • 自行处理 history,无法利用 Next.js 的 router 机制

如果你用 getServerSideProps,则加载数据逻辑都写在这个函数中就好。从第一本书跳转第二本书时,前端会访问 Next.js 的 server,以 JSON 方式获得服务端调用 getServerSideProps 的结果,作为 props 传入给 page component。整个逻辑写在一处就好,相较于客户端方式需要自行处理各种状态,心智负担明显变小。