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

相關文章
相關標籤/搜索