浅析EggJS接入NextJS

2,314 阅读9分钟

需要说明的是,本文并非说Next的使用方式或者Egg的使用方式,建议阅读者对Egg和Next有一定了解。本文主要想表达的是对Next的一些吐槽,已经如何和Egg配合使用。

最近在思考着一个问题,前端从曾经的php或者java等后端通过模板引擎渲染页面到浏览器,到现在的react,vue,ng等mvc/mvvm框架,采用异步数据请求数据,客户端渲染页面。在我看来其实是一种进步。但是自从Node出来后,又搞了个SSR,或者说是服务器同构吧。感觉就是回到原点,只是换个语言而已。

我也知道SSR的意义在于有利于优化SEO,优化白屏速度,但是同时如果我们的网站对SEO有要求的话,那么就不得不使用SSR技术了。但是对于服务器的压力其实也会增加压力,所以使用SSR还是按需吧。个人的一点感觉,如果对SEO有很强的要求的话,感觉公司的规模有限,那么使用Node的小公司又有多少呢?例如淘宝京东等,与其做SEO还不如直接给钱搜索引擎供应商买排名来得更加直接。当然这是题外话,正所谓技多不压身,对于一个前端,使用Node慢慢变成了一个刚需,那当然要了解一下SSR的用法,以备不时之需。

对Next做了几个Demo之后,总结出了一些问题。

  1. Next对于React的构建,包切割等的webpack配置其实都做好了大量的配置,理论上其实我们不需要修改什么或者扩展什么,但是如果你的项目是旧项目而并非新项目,可能你自己的webpack也配置了一大堆配置,那么可能你就需要花些时间去兼容一下Next的webpack配置了,这里就有第一个问题,Next里面到底有什么配置,会和什么其他配置冲突呢?查阅文档后,貌似没有详细的说明。
  2. 文档中只说明就基本的使用方式,并没有说明API的使用方式,估计作者可能希望开发者只需要关注使用就可以,并不需要去较真原理以及API的使用方式。这真的好吗?
  3. 整个官网,并没有详细说明Next如何结合Express或者Koa的使用,一个项目也不可能就使用Next去替代Koa或者Express的作用吧?毕竟Next的定位应该是负责view渲染。

因为我是使用Egg的,既然也没有详细说明如何和Koa去配合使用,难道我还希望作者能告诉我怎么和Egg配合使用吗?那我就去问Egg的开发人员看看有没有好一点的例子。然后....得到的回复既然是!

![](https://pic2.zhimg.com/80/v2-714cc991d9b3f8684f1a8c3e02c72a9d_720w.jpg)

呵呵!既然这样我就自己摸索一下咯!

查阅文档发现Next有去说道如何自定义启动一个Next,以下是官方例子:

// This file doesn't go through babel or webpack transformation.
// Make sure the syntax and sources this file requires are compatible with the current node version you are running
// See https://github.com/zeit/next.js/issues/1245 for discussions on Universal Webpack or universal Babel
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')

const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare().then(() => {
  createServer((req, res) => {
    // Be sure to pass `true` as the second argument to `url.parse`.
    // This tells it to parse the query portion of the URL.
    const parsedUrl = parse(req.url, true)
    const { pathname, query } = parsedUrl

    if (pathname === '/a') {
      app.render(req, res, '/b', query)
    } else if (pathname === '/b') {
      app.render(req, res, '/a', query)
    } else {
      handle(req, res, parsedUrl)
    }
  }).listen(3000, err => {
    if (err) throw err
    console.log('> Ready on http://localhost:3000')
  })
})

可能我水平问题,青涩难懂。因为Egg的启动并不一样,Egg对启动做了封装。那么我就尝试一下兼容进去吧。查了一下Egg的文档,启动的一些生命周期,决定在beforeStart中实例化Next。

![](https://pic3.zhimg.com/80/v2-5625521326731e0b3bc41cb1179c3a5c_720w.jpg)

启动发现没有问题,那就尝试一下编写一个controller去渲染一个页面。

写一个路由

![](https://picb.zhimg.com/80/v2-6fc85a4b3184c294d065bb7f34e0a879_720w.jpg)

写一个controller

![](https://pic1.zhimg.com/80/v2-dce3be05e9cfc41335772dec41157035_720w.jpg)

走你!

![](https://picb.zhimg.com/80/v2-9dc52940e2eb7cf6087c2ff1c167c3b1_720w.jpg)

WTF!!!!

经过断点,发现render函数返回的是一个Promise,既然这样我就加一个await吧。

![](https://pic3.zhimg.com/80/v2-ab9753a3da4ac5a59d8c4b3c19728bce_720w.jpg)

成功渲染,但是经过断点发现render返回的居然是undefined,那么它是如何渲染到页面的。直接告诉我render里面有东西!抱着求知的心我断点进去了一下代码,发现还真有东西!断点进去render函数发现最后是调用了一个sendHTML的函数,然后发现sendHTML函数帮我们做了一切响应的事情了!

以下是断点的代码,分别是next-server.js和render.js

next-server的render函数

![](https://picb.zhimg.com/80/v2-3cff3c0d77d992e477f38b98cfaaed70_720w.png)

render的sendHTML函数

![](https://pic3.zhimg.com/80/v2-3f165a94f8f0aa081206a55427ad916d_720w.jpg)

附上github的源码地址:

next-server​

sendHTML​

至此明白了Next为我们做的真多。顺带一提,Next使用了res.end()的方式返回数据,经过验证,Koa的洋葱模型的中间件触发正常。但是在使用Egg的时候,在没有用Next之前,我们需要对HTML模板做一些数据的提前注入,例如一些模板数据等,我们就用到egg-view这个插件,使用方式都是渲染出一个html字符串,放到ctx.body中进行返回的。如果需求我们渲染完html字符串,还需要做一些特殊处理的一些需求的时候,使用Next的render就并不合适了。

egg的模板渲染方式(只是一些demo)

![](https://picb.zhimg.com/80/v2-484662d8696de1064520114f0e5b869a_720w.jpg)

通过阅读文档貌似没有发现返回html字符串的API,那么我们就继续看源码咯,在调用render的时候,发现一个惊喜。

![](https://pic1.zhimg.com/80/v2-0ab97a9a71fb9cfa5baff46f254ec121_720w.jpg)

一个叫renderToHTML的函数,经过断点,发现确实返回出来的是一个经过编译后的html字符串,这就满足了需求了!在看了一下,这个API并非内部API,而是暴露出来的,那就意味着我们能使用了。

![](https://picb.zhimg.com/80/v2-982548e45e11254c230883853cba4c26_720w.jpg)

github地址:

zeit/next.js​

既然这样我们就修改成更像使用egg-view的方式吧!

![](https://pic1.zhimg.com/80/v2-b6e9c8089267fc6e53c00df624f4f6a0_720w.jpg)
![](https://pic1.zhimg.com/80/v2-48ac8a38cfb4edb2e4c195ad6a94a222_720w.jpg)

验证通过!

其实Next的render内部也是调用renderToHTML,然后不返回出来,并且内部帮我们处理好返回的逻辑,添加上响应头的一些信息而已,所以理论上使用render和renderToHTML应该是没有任何区别的,都是能得到html字符串的。

还有一个问题不得不提的就是Next本来构建后会在_next文件夹下生成文件,通过页面依赖_next文件夹下的文件进行引入,所以必须要在Egg的路由中添加以下配置:

![](https://pic3.zhimg.com/80/v2-c3da2ee1a3b0c1bfd98e6cde187a1808_720w.jpg)

并且在对应的controller中使用handle函数

![](https://pic3.zhimg.com/80/v2-7c0f7c071e4cae8f5aef9e1a6f2a821b_720w.jpg)

至此已经基本将Next接入到Egg中了。但是喜欢折腾的我,怎么会如此完事呢。这个handle是什么东西?

首先这个handle是通过在Egg启动Next并将其实例化后挂载在app中的。

![](https://pic3.zhimg.com/80/v2-e1ae85fc43e16ddf814517ac7618a536_720w.jpg)

那么这个是什么东西呢?我们将路由没有命中的全部指向了一个专门处理next生成文件的返回的controller中,然后我们并没有告诉这个handle函数任何需要返回的路径,只是单单的调用了一下,然后就实现了对应资源的返回了。我们又来断点看看源码。

首先我们在启动的时候调用了getRequestHandler函数,返回了一个handle函数。

![](https://pic2.zhimg.com/80/v2-75aeccfa53b550ab45bbc16d97eae2fa_720w.jpg)

然后我们在controller中调用了handle之后发生了什么事情呢?

![](https://pic4.zhimg.com/80/v2-25ffb5dc8c35c0c2c9ee96ee2f3744c1_720w.jpg)

当我们调用的时候,需要传入req和res到函数内,当然还有第三个参数,里面可以传入对应数据。之后内部经过一番格式化后,取到req的url值,然后传入了一个run函数内。

![](https://pic4.zhimg.com/80/v2-3afe75842e2c9b1e07437de741f11ce0_720w.jpg)

传入了run马上调用了一个router.match的方法,从名字上判断应该是通过Next内部自己的路由去匹配当前req的url然后返回对应的内容。我们都知道如果我们只是单纯的使用Next的情况下,它其实自己是有一个路由系统的,所有页面都是通过对应url然后在pages里面去找对应的页面,然后Next自己内部处理了_next开头的url到next文件夹中获取资源文件的。所以由此明白为何Next官方说道_next的路径和pages不要去修改的原因,就是因为内部做好了这一系列的配置导致的。(感觉扩展不好)

下面是router的源码:

router​

由此我们基本明白了为何我们使用handle函数可以匹配到对应的我们需要的文件资源了。因为handle内部根据当前的req.url去匹配自身根据_next和pages文件对应的路径。通俗理解就是当Egg自身的路由都不命中的情况下,写一个匹配任何不命中路由的请求,然后调用handle去尝试匹配Next自己的路由配置看能否命中。

回头看Next官网的文档中的自定义启动的demo就完全明白它想表达的意思了。

![](https://pic4.zhimg.com/80/v2-123e65f2be94a6067f7873c1f8ff52e7_720w.jpg)

至此,Egg接入Next基本完成,当然如果要细说的话,接入Egg后,怎么去监控Next的报错等等。但是现在还没有正式投入到生产中,日后投入生产后再进行后续的踩坑总结。

总结

  • Next不得不吐槽就是文档了,只有基本的使用,并没有详细的API使用。
  • 高度封装的原因,对于业务的多样性,能否兼容那么多不同的业务场景需要打一个问号。
  • 对于服务器的压力会有多大的影响,貌似没有找到很好的数据支撑。
  • 接入Egg不难,但是可能对于新项目使用比较友好,毕竟内置了webpack的一大堆配置,配合旧项目可能会出现比较头痛的情况。

到这里基本就爬出了个坑了,但是在各大网站都查不到Egg和Next的配合使用,不知道我自己这样用是否合适,会不会有什么问题,希望有大牛提一下建议!万分感谢!

转载自我的知乎:zhuanlan.zhihu.com/p/54841918