幾乎一全年沒咋寫文章,主要是懶,加上工做也挺忙。可是想趁着年末發一篇,但願明年更勤奮一點。其實不是沒東西寫,就是想深刻一個東西仍是很困難的,要查各類資料,最終仍是懶就是了。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
其次是沒有官方實現具名路徑,什麼是具名路徑呢?就是/movie/:id
這裏這種形式,我的感受nextjs在這方面是追隨react-router4的。vuejs的同構框架nuxtjs則不存在這個問題,由於vue-router自己也是統一管理路由的。先不說這種狀況的好壞,仍是找找解決方案吧。web
根據我找到的實例和文檔,目前有兩種解決方案:redis
下圖能夠看到其實在nextjs router裏query是存在的。vue-router
那咱們須要訪問具名路由頁面的時候能夠這麼寫, 將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 ) } }
使用query傳參數過去確實能夠解決問題,可是太不優雅,與rest的思想也不太符合。因此next社區找到了另外一個解決方案,使用custom server。
在說具體方案以前咱們咱們能夠了解一下,說到底nextjs並非一個生成靜態資源的腳手架,next最終仍是要單獨部署node服務的。也就是nextjs其實內置了一個http服務,若是咱們不使用custom sever的話,內置服務仍是能夠很好的幫咱們完成渲染頁面的任務。
可是若是咱們的node不只僅是渲染頁面,還須要寫接口。那麼這時候的狀況就很相似傳統後端的開發模式了:不只僅須要寫接口還須要渲染頁面。
很顯然nextjs的內置http服務是沒法完成這個任務的,咱們須要更加完善的web 框架。畢竟專業的事仍是交給專業的。這時候就是custom server大顯身手的時候了。nextjs裏也有一系列的例子:
那麼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