nextjs踩坑

Next.js踩坑

几乎一全年没咋写文章,主要是懒,加上工做也挺忙。可是想趁着年末发一篇,但愿明年更勤奋一点。其实不是没东西写,就是想深刻一个东西仍是很困难的,要查各类资料,最终仍是懒就是了。javascript

next.js是react的同构库,不少文章里把他看成一个脚手架,也不是不行,可是我的认为next.js比通常的脚手架能作的更多,但也有局限性。这两天下班回去实践了一下nextjs的开发。遇到一些坑,也有一些收获这里记录一下。html

请求数据

nextjs没有客户端的生命周期,只有一个静态方法getInitialProps,因此获取接口数据也只能在这个方法里了。getInitialProps的返回数据就做为该组件的props。getInitialProps有两个参数:req和res,也就是咱们很是熟悉的http参数。说句题外话,现有的node web框架都是req做为输入,res做为输出,增长各类中间件。vue

因此我的以为nextjs的组件形式太适合无状态组件了。下面是一个简单的例子代码:java

// 获取电影列表并渲染
class MovieList extends Component {
    static async getInitialProps(){
      const data = await getMovieList()
    return {
        list: data
    }
  }
  
  render () {
      return (
        <div>
        {this.props.list.map(movie => {
            <MovieCard key={movie.id} movie={movie}>
        })}
      </div>
    )
  }
}

固然这里最终仅仅是服务端输出的列表,咱们可能还会有其余操做,好比删除加载下一页之类的,可是这些操做都不必在服务端操做的。添加几个相应的方法便可。node

路由管理

nextjs的路由是基于文件系统的,至关清晰和简单,好比在pages文件夹下面增长一个movie-detail组件,并写上相应的代码,咱们就能够访问/movie-detail 这个路由了。起初以为这样的路由形式实在太优雅了,可是用久了就会发现不少问题。react

路由嵌套

首先是嵌套路由,好比我想创建/user/profile这个路由,这个其实很好解决,就是在pages文件夹下面依次嵌套就好了:webpack

image.png

具名路由

其次是没有官方实现具名路径,什么是具名路径呢?就是/movie/:id这里这种形式,我的感受nextjs在这方面是追随react-router4的。vuejs的同构框架nuxtjs则不存在这个问题,由于vue-router自己也是统一管理路由的。先不说这种状况的好坏,仍是找找解决方案吧。web

根据我找到的实例和文档,目前有两种解决方案:redis

使用query代替具名路

下图能够看到其实在nextjs router里query是存在的。vue-router

image.png

那咱们须要访问具名路由页面的时候能够这么写, 将id用query传过去/movie-detail?id=xxx

// 电影详情页面

class MovieDetail extends Component {
    static async getInitialProps({ req }) {
    const { id } = req.query
    const detail = await getDetail(id) 
      return {
        detail
    }
  }
  
  render () {
      return (
        // do anything you want
    )
  }
}

custom server 解决

使用query传参数过去确实能够解决问题,可是太不优雅,与rest的思想也不太符合。因此next社区找到了另外一个解决方案,使用custom server。

在说具体方案以前咱们咱们能够了解一下,说到底nextjs并非一个生成静态资源的脚手架,next最终仍是要单独部署node服务的。也就是nextjs其实内置了一个http服务,若是咱们不使用custom sever的话,内置服务仍是能够很好的帮咱们完成渲染页面的任务。

可是若是咱们的node不只仅是渲染页面,还须要写接口。那么这时候的状况就很相似传统后端的开发模式了:不只仅须要写接口还须要渲染页面。

很显然nextjs的内置http服务是没法完成这个任务的,咱们须要更加完善的web 框架。毕竟专业的事仍是交给专业的。这时候就是custom server大显身手的时候了。nextjs里也有一系列的例子:
image.png

那么custom server是如何解决具名路径的问题的呢?咱们是借用nextjs的渲染能力。这里以express为例,具体代码以下:

// server.js
const express = require('express')
const next = require('next')

const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev, quiet: false })
const handle = app.getRequestHandler()
const SERVE_PORT = process.env.SERVE_PORT || 8001

app.prepare().then(() => {
  const server = express()

  server.get('/movie-detail/:id', async (req, res) => {
    // 渲染movie-detail这个组件
    const html = await app.renderToHTML(req, res, '/movie-detail', req.query)
    res.send(html)
  })

  server.get('*', (req, res) => handle(req, res))

  server.listen(SERVE_PORT, err => {
    if (err) throw err
    console.log(`> Ready on http://localhost:${SERVE_PORT}`)
  })
})

上面是server.js的简略代码,固然在组件里咱们也要作相应处理,代码以下

// /pages/movie-detail.jsx
// 电影详情页面

class MovieDetail extends Component {
    static async getInitialProps({ req }) {
    const { id } = req.params
    const detail = await getDetail(id) 
      return {
        detail,
      id
    }
  }
  
  render () {
      return (
        // do anything you want
    )
  }
}

页面缓存

对于csr的的react应用来讲,渲染耗时100ms并非什么太大问题,可是到了服务端,100ms很明显是无法忍受的。首先客户端渲染并不会形成服务器资源的浪费,其实也不会对服务器形成太大鸭梨。可是服务端就不同了。一旦用户量大了,势必会引发各类问题,因此页面缓存仍是颇有必要的。

具体页面缓存在哪里并非咱们考量的范围,一样页面缓存也须要用到custom server,具体服务端框架自定吧。这里以lru-cache为例作一个简单的页面缓存,其实换成其余的诸如redis也是没有任何问题的。

const dev = process.env.NODE_ENV !== 'production'

const next = require('next')
const express = require('express')
const LRUCache = require('lru-cache')

const ssrCache = new LRUCache({
  max: 1000, // cache item count
  maxAge: 1000 * 60 * 60, // 1 hour
})

const app = next({ dev, quiet: false })

const handle = app.getRequestHandler()

const SERVE_PORT = process.env.SERVE_PORT || 8001

app.prepare().then(() => {
  const server = express()

  server.get('/', async (req, res) => {
    renderAndCache(req, res, '/', { ...req.query })    
  })

  server.get('/movie-detail/:id', async (req, res) => {
    renderAndCache(req, res, '/movie-detail', { ...req.query })
  })

  server.get('*', (req, res) => handle(req, res))

  server.listen(SERVE_PORT, err => {
    if (err) throw err
    console.log(`> Ready on http://localhost:${SERVE_PORT}`)
  })
})

const getCacheKey = req => `${req.url}`

// 缓存并渲染页面,具体是从新渲染仍是使用缓存
async function renderAndCache(req, res, pagePath, queryParams) {
  const key = getCacheKey(req)
  if (ssrCache.has(key)) {
    res.setHeader('x-cache', 'HIT')
    res.send(ssrCache.get(key))
    return
  }

  try {
    const html = await app.renderToHTML(req, res, pagePath, queryParams)

    // Something is wrong with the request, let's skip the cache
    if (res.statusCode !== 200) {
      res.send(html)
      return
    }

    // Let's cache this page
    ssrCache.set(key, html)

    res.setHeader('x-cache', 'MISS')
    res.send(html)
  } catch (err) {
    app.renderError(err, req, res, pagePath, queryParams)
  }
}

其中renderAndCache是关键。这里判断页面是否有缓存,若是有的话则直出缓存内容。不然的话就从新渲染。至于缓存时间还有缓存大小看我的设置了,这里不赘述了。

部署上线

部署上线这一块实在没什么好说的,简单的话直接起一个node服务的就能够,复杂一点就要包括报警重启等等,都是看我的状况的。

我的习惯使用supervisor启动node服务。

总结

说了上面那么多,其实官方文档里都有相关例子,就当个人我的踩坑记录吧。

对于nextjs来讲,我认为若是是展现型的应用,就应该放心大胆的用起来。不光开发快还爽,同时屏蔽webpack配置,有什么理由不用?

若是是功能性的,好比一系列的绘图组件则完成不必使用了,对于canvas之类的仍是必须用客户端渲染,然而nextjs又没有生命周期,用nextjs可能会至关坑。

对于我的开发这我则是至关推荐。何须去配置webpack浪费生命啊。

若是是彻底静态的应用,我推荐gatsbyjs。具体怎么使用则是另一个话题了。

若有谬误,轻点喷。 over

相关文章
相关标签/搜索