使用next.js完成從開發到部署

GoDotDotDot發表於2018-05-25

next.js 是一個非常棒的輕量級的react同構框架,使用它可以快速的開發出基於服務端渲染的react應用。在next.js 官網推薦的是使用now來部署應用,但是對於國內使用者或者說是有特殊需求的使用者來說,部署到自定義伺服器也許是大多數人希望的。藉著近期公司官網改版,順便分享下自己從開發到部署過程中所經歷的點點滴滴。

依稀還記得第一次使用next.js 是在去年(2017年),那個時候使用的是next.js 2.x版本,react還是15版本,一年過去,現在react已經發展到16版本,而next.js 已經發展到6.0版本了,迭代速度瞠目結舌,在使用新版本的過程中也是遇到不少的坑。

用到的技術

先說下這次用到了哪些技術,下面列舉了專案中主要用到的技術或工具庫。

由express原班人馬開發的下一代web框架,用來提供web服務。

是一個高效能的HTTP和反向代理伺服器,也是一個IMAP/POP3/SMTP伺服器(摘自百度百科),由俄羅斯人開發。用來提供靜態檔案服務、https證照、代理服務。

一個javascript ui庫

一個輕量級的react同構應用框架

由螞蟻金服開發的基於react的一套中後臺產品元件庫

基於react的動畫解決方案

判斷元件是否在當前可視區的react 元件

一個帶有負載均衡功能的Node應用的程式管理器

同構WHATWG Fetch API

開發階段

講了這麼多,讓我們進入開發階段,第一步構建專案架構,這裡分享下自己的專案結構:

? .vscode

vscode 配置檔案

? component

react元件

? common

公共部分,我放置的是導航欄資訊、全域性變數和全域性樣式等等

? pages

專案所有頁面入口,也是next.js 各頁面入口檔案

? static

靜態檔案

? styles

各頁面樣式表

? index.js

node啟動檔案

? .babelrc

babel配置檔案

? .gitignore

git 配置檔案

? ecosystem.config.js

pm2配置檔案

? next.config.js

next.js 配置檔案

? postcss.config.js

postcss 配置檔案

? nginx.conf

nginx配置檔案

? package.json

npm配置檔案

在完成了專案結構配置之後,假設你已經在package.json中儲存了我們所需要的所有依賴,讓我們嘗試著輸入yarn來安裝依賴。這裡假設安裝一切順利,下面繼續我們的開發之旅。

首先,在pages檔案下新建一個index.js,這裡就隨便從我真實專案中抽取部分程式碼來作師範。

export default class HomePage extends React.Component {
  static async getInitialProps({ req, pathname }) {
    const data = await fetch(`${ctx}/api/projects/common/list`).then(res => res.json())
    .then(dt => dt)
    .catch(err => {
      return {
        success: false,
        message: err.message
      }
    })
    return { pathname,data };
  }

  render() {
    const { pathname, data } = this.props;
    return (
      <div>
         <Head>
          <title>首頁-易科捷(武漢)生態科技有限公司</title>
        </Head>
        <div>Welcome to next.js!</div>
    	{/*這裡省略程式碼*/}
      </div>
    );
  }
}

複製程式碼

如果你的package.json中沒有配置next啟動指令碼,請訪問setup進行配置,下面我們在控制檯執行npm run dev,如果一切順利,開啟瀏覽器,你將會看到Welcome to next.js!

next.js中開發體驗和react幾乎沒有什麼區別,但是在webpack配置這塊可能需要下點功夫。一些常用的外掛像sasscss等, next.js都已經給你提供了,你也可以使用社群開源的外掛來完成你的開發之旅。詳情請檢視next.js官網

部署

在經歷了開發階段、測試等等一系列流程,現在終於等到了部署階段。在next.js中生產階段打包只需要執行npm run build即可,官方推薦不修改打包的資料夾名字(原名稱為.next),這裡個人推薦修改成build或者dist這些名稱。在打包完成之後,需要編寫nodejs啟動入口檔案,下面貼出例項程式碼:

const Koa = require('koa')
const next = require('next')
const Router = require('koa-router')

const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare()
  .then(() => {
    const server = new Koa()
    const router = new Router()
	// 首頁
    router.get('/', async ctx => {
      await app.render(ctx.req, ctx.res, '/', ctx.query)
      ctx.respond = false
    })
	// 關於
    router.get('/about', async ctx => {
      await app.render(ctx.req, ctx.res, '/about', ctx.query)
      ctx.respond = false
    })
	// 產品
    router.get('/products/:id', async ctx => {
      const {id} = ctx.params
      await app.render(ctx.req, ctx.res, `/products/${id}`, ctx.query)
      ctx.respond = false
    })
	// 案例
    router.get('/case', async ctx => {
      await app.render(ctx.req, ctx.res, '/case', ctx.query)
      ctx.respond = false
    })
	// 聯絡我們
    router.get('/contact', async ctx => {
      await app.render(ctx.req, ctx.res, '/contact', ctx.query)
      ctx.respond = false
    })
	// 詳情
    router.get('/view/:type/:id', async ctx => {
      const {id, type} = ctx.params
      await app.render(ctx.req, ctx.res, `/view`, {id, type})
      ctx.respond = false
    })
    // 如果沒有配置nginx做靜態檔案服務,下面程式碼請務必開啟
   /* router.get('*', async ctx => {
      await handle(ctx.req, ctx.res)
      ctx.respond = false
    })*/
    // 防止出現控制檯報404錯誤
    server.use(async (ctx, next) => {
      ctx.res.statusCode = 200
      await next()
    })
    server.use(router.routes())
    server.listen(port, () => {
      console.log(`> Ready on http://localhost:${port}`)
    })
  })

複製程式碼

一般的靜態檔案、gzip壓縮無需交給nodejs來做,個人一直認為專業的事交給專業的人。這裡將該項任務轉移給nginx,特別注意上面例項程式碼中我註釋的部分程式碼,若果你沒有使用nginx來做靜態檔案服務,請務必開啟,否則像next.js打包出來的jscss、圖片檔案等,都將報404

next.js生產打包階段打包出來的js檔案請求路徑中帶有版本號,而真實打包出來的資料夾卻沒有實際對應的目錄,也就是打包出來的是虛擬目錄,這裡如果使用nginx就需要特別注意。好在next.js提供配置項來修改build id,以下是我的真實程式碼:

// next.config.js
module.exports = {
  generateBuildId: async () => {
    // For example get the latest git commit hash here
    return 'v1'
  }
}
複製程式碼

這樣打包出來的虛擬路徑大概是_next/v1/page/xxx.js,如果你使用cdn字首,這裡有一點區別,但是版本號依然存在。

還有一個坑就是next.js打包出來的有三個資料夾:bundlesdiststatic,對於不知道原始碼的人來說,根本不知道實際請求檔案在哪一個資料夾。於是我就看next.js原始碼,發現其實找的是bundle檔案下的page,原始碼位置:L214

所以在配置nginx就需要使用別名。下面給出一段我的nginx真實配置程式碼:

		# 網站根目錄檔案
        location ~ ^/(robots.txt|humans.txt|favicon.ico|sw.js|baidu_verify_7Kj6tQjI3v.html) {
            root /home/website/eco_website_pc/static/;
            if ($request_filename ~* sw.js){
                    expires -1s;
                }
            expires 10m;

	    }
        # static下的檔案
        location ^~ /static/ {
            alias /home/website/eco_website_pc/static/;
            if ($request_filename ~* sw.js){
                    expires -1s;
                }
            expires 10m;

	    }
        # next pages頁面下的指令碼
        location ~ ^/(/_next/v1/) {
            alias /home/website/eco_website_pc/build/bundles/;
            if ($request_filename ~* sw.js){
                    expires -1s;
                }
            expires 10m;

	    }
        # next static下的靜態檔案
        location ~ ^/(/_next/static/) {
            root /home/website/eco_website_pc/build;
            if ($request_filename ~* sw.js){
                    expires -1s;
                }
            expires 10m;

	    }
複製程式碼

靜態檔案配置好了就需要配置https證照了,因為我們這次專案是公司官網,證照我就自己去免費弄了一個,這裡我使用的freessl上面提供的亞洲誠信的證照。在申請完ssl證照之後需要去域名提供商那裡去配置TXT記錄,我這裡使用的是阿里雲,在完成驗證後,freessl將會下載證照,拿到該證照之後需要去配置nginx ssl證照,下面貼出我的完整配置:

server {
        listen       80;
        listen       443 ssl;
        server_name  wh-eco.com;
        charset utf-8;
        ssl_certificate /home/website/ssl/www/full_chain.pem;
        ssl_certificate_key /home/website/ssl/www/private.key;
        fastcgi_param   HTTPS               on;
        fastcgi_param   HTTP_SCHEME         https;

        if ($scheme = http ) {
          return 301 https://$host$request_uri;
        }
        access_log      /var/log/nginx/www.wh-eco.com.access.log;
        error_log       /var/log/nginx/www.wh-eco.com.error.log;
        location / {
            proxy_pass http://127.0.0.1:xxxx; #保密 0.0
            proxy_set_header Host $host;
            #proxy_redirect off;
            proxy_set_header    REMOTE-HOST $remote_addr;
        	# 網站可能後期會使用websocket 特次升級請求協議
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	        proxy_connect_timeout 60;
            proxy_read_timeout 600;
            proxy_send_timeout 600;
        }
        # 網站根目錄檔案
        location ~ ^/(robots.txt|humans.txt|favicon.ico|sw.js|baidu_verify_7Kj6tQjI3v.html) {
            root /home/website/eco_website_pc/static/;
            if ($request_filename ~* sw.js){
                    expires -1s;
                }
            expires 10m;

	    }
        # static下的檔案
        location ^~ /static/ {
            alias /home/website/eco_website_pc/static/;
            if ($request_filename ~* sw.js){
                    expires -1s;
                }
            expires 10m;

	    }
        # next pages頁面下的指令碼
        location ~ ^/(/_next/v1/) {
            alias /home/website/eco_website_pc/build/bundles/;
            if ($request_filename ~* sw.js){
                    expires -1s;
                }
            expires 10m;

	    }
        # next static下的靜態檔案
        location ~ ^/(/_next/static/) {
            root /home/website/eco_website_pc/build;
            if ($request_filename ~* sw.js){
                    expires -1s;
                }
            expires 10m;

	    }
        error_page   500 502 503 504 = /error.html;
        error_page  404 = /notfound.html;
        location = /error.html {
                root   /home;
            }
        location = /notfound.html{
            root  /home;
        }
    }
複製程式碼

至於gzip你可以根據你要求來做配置,貼一個我的示例配置:

 	gzip  on;
    gzip_comp_level 6;
    gzip_vary on;
    gzip_types
        application/atom+xml
        application/javascript
        application/json
        application/rss+xml
        application/vnd.ms-fontobject
        application/x-font-ttf
        application/x-web-app-manifest+json
        application/xhtml+xml
        application/xml
        font/opentype
        image/svg+xml
        image/x-icon
        image/jpeg 
        image/gif 
        image/png
        text/css
        text/plain
        text/x-component;
複製程式碼

在完成nginx配置之後需要做的是以pm2方式啟動整個應用

pm2 start ecosystem.config.js
複製程式碼

在執行完上述命令後,如果一切順利,你就可以輸入域名來訪問你的應用了(假設你已經完成了域名解析工作)。

總結

一入前端深似海


Github | 部落格

相關文章