React服務端渲染+pm2自動化部署

上帝的眼發表於2019-03-04

本文是直接著手SSR部分的並通過實戰講述自己遇到的一些問題和方案,需要大家有一定的React,node和webpack基礎能力。skr,skr。

服務端渲染

Server Slide Rendering服務端渲染,又簡寫為SSR,他一般被用在我們的SPA(Single-Page Application),即單頁應用。

為什麼要用SSR?

首先我們需要知道SSR對於SPA的好處優勢是什麼。

  • 更好的SEO(Search Engine Optimization)SEO是搜尋引擎優化,簡而言之就是針對百度這些搜尋引擎,可以讓他們搜尋到我們的應用。這裡可能會有誤區,就是我也可以在index.html上寫SEO,為什麼會不起作用。因為React、Vue的原理是客戶端渲染,通過瀏覽器去載入js、css,有一個時間上的延遲,而搜尋引擎不會管你的延遲,他就覺得你如果沒載入出來就是沒有的,所以是搜不到的。
  • 解決一開始的白屏渲染,上面講了React的渲染原理,而SSR服務端渲染是通過服務端請求資料,因為服務端內網的請求快,效能好所以會更快的載入所有的檔案,最後把下載渲染後的頁面返回給客戶端。

上面提到了服務端渲染和客戶端渲染,那麼它們的區別是什麼呢?

客戶端渲染路線:

  1. 請求一個html
  2. 服務端返回一個html
  3. 瀏覽器下載html裡面的js/css檔案
  4. 等待js檔案下載完成
  5. 等待js載入並初始化完成
  6. js程式碼終於可以執行,由js程式碼向後端請求資料( ajax/fetch )
  7. 等待後端資料返回
  8. react-dom( 客戶端 )從無到完整地,把資料渲染為響應頁面

服務端渲染路線:

  1. 請求一個html
  2. 服務端請求資料( 內網請求快 )
  3. 伺服器初始渲染(服務端效能好,較快)
  4. 服務端返回已經有正確內容的頁面
  5. 客戶端請求js/css檔案
  6. 等待js檔案下載完成
  7. 等待js載入並初始化完成
  8. react-dom( 客戶端 )把剩下一部分渲染完成( 內容小,渲染快 )

其主要區別就在於,客戶端從無到有的渲染,服務端是先在服務端渲染一部分,在再客戶端渲染一小部分

我們怎麼去做服務端渲染?

我們這裡是用express框架,node做中間層進行服務端渲染。通過將首頁進行同構處理,讓服務端,通過呼叫ReactDOMServer.renderToNodeStream方法把Virtual DOM轉換成HTML字串返回給客戶端,從而達到服務端渲染的目的。

這裡專案起步是已經做完前端和後端,是把已經寫好的React Demo直接拿來用

服務端渲染開始

既然是首頁SSR,首先我們要把首頁對應的index.js抽離出來放入我們服務端對應的server.js,那麼index.js中元件對應的靜態css和js檔案我們需要打包出來。

用webpack打包檔案到build資料夾

我們來執行npm run build

React服務端渲染+pm2自動化部署

我們可以看到兩個重要的資料夾,一個是js資料夾,一個是css資料夾,他就是我們專案的js和css靜態資原始檔

將打包後的build檔案能在服務端server.js中訪問到

因為是服務端,我們需要用到express

import express from `express`
import reducers from `../src/reducer`;

import userRouter from `./routes/user`
import bodyParser from `body-parser`
import cookieParser from `cookie-parser`
import model from `./model`
import path from `path`
import https from `http`
import socketIo from `socket.io`


const Chat = model.getModel(`chat`)
//新建app
const app = express()

//work with express
const server = https.Server(app)
const io = socketIo(server)
io.on(`connection`,function(socket){
  socket.on(`sendmsg`,function(data){
    let {from,to,msg} = data
    let chatid = [from,to].sort().join(`_`)
    Chat.create({chatid,from,to,content:msg},function(e,d){
      io.emit(`recvmsg`,Object.assign({},d._doc))
    })
    // console.log(data)
    // //廣播給全域性
    // io.emit(`recvmsg`,data)
  })
})

app.use(cookieParser())
app.use(bodyParser.json())
app.use(`/user`,userRouter)
app.use(function(req,res,next){
  if(req.url.startsWith(`/user/`) || req.url.startsWith(`/static/`)){
    return next()
  }
  //如果訪問url根路徑是user或者static就返回打包後的主頁面
  return res.sendFile(path.resolve(`build/index.html`))
})
//對映build檔案路徑,專案上要使用
app.use(`/`,express.static(path.resolve(`build`)))


server.listen(8088, function () {
    console.log(`開啟成功`)
})
複製程式碼
  • 主要看上面的app.use(`/`,express.static(path.resolve(`build`)))res.sendFile(path.resolve(`build/index.html`))這兩段程式碼。
  • 他們把打包後的主頁放入服務端程式碼中返回給客戶端。
  • 因為上面我用了import程式碼,所以我們在開發環境中需要用到babel-cli裡的babel-node來編譯。
  • 安裝npm --registry https://registry.npm.taobao.org i babel-cli -S`,大家如果覺得這樣切換源麻煩,可以下個nrm,360度無死角切換各種源,好用!
  • 我們需要修改package.json的啟動伺服器的npm scripts"server": "NODE_ENV=test nodemon --exec babel-node server/server.js"
  • cross-env跨平臺設定node環境變數的外掛。
  • nodemon和supervisor一樣是watch服務端檔案,只要一改變就會重新執行,相當於熱過載。nodemon更輕量
  • 最後我們來跑一下npm run server,就能看到服務端跑起來了。
React服務端渲染+pm2自動化部署

ReactDOMServer.renderToString/ReactDOMServer.renderToNodeStream

  • 這裡我們先講一下在瀏覽器中React.createElement把React的類進行例項化,例項化後的元件可以進行mount,最後通過React.render渲染到我們的客戶端瀏覽器介面。
  • 而在伺服器中我們可以通過 renderToString或者renderToNodeStream方法把React例項化的元件,直接渲染生成html標籤。那麼這倆個有什麼區別呢?
  • renderToNodeStream是React 16最新發布的東西,它支援直接渲染到節點流。渲染到流可以減少你的內容的第一個位元組(TTFB)的時間,在文件的下一部分生成之前,將文件的開頭至結尾傳送到瀏覽器。 當內容從伺服器流式傳輸時,瀏覽器將開始解析HTML文件。速度是renderToString的三倍,所以我們在這裡使用renderToNodeStream
import express from `express`
import React from `react`
import {renderToStaticMarkup,renderToNodeStream} from `react-dom/server`

import thunk from `redux-thunk`;
import { Provider } from `react-redux`;
import {StaticRouter} from `react-router-dom`
import {
  createStore,
  applyMiddleware,
  //組合函式用的
  compose
} from `redux`;
import App from `../src/App`
import reducers from `../src/reducer`;

import userRouter from `./routes/user`
import bodyParser from `body-parser`
import cookieParser from `cookie-parser`
import model from `./model`
import path from `path`
import https from `http`
import socketIo from `socket.io`

const Chat = model.getModel(`chat`)
//新建app
const app = express()

//work with express
const server = https.Server(app)
const io = socketIo(server)
io.on(`connection`,function(socket){
  socket.on(`sendmsg`,function(data){
    let {from,to,msg} = data
    let chatid = [from,to].sort().join(`_`)
    Chat.create({chatid,from,to,content:msg},function(e,d){
      io.emit(`recvmsg`,Object.assign({},d._doc))
    })
    // console.log(data)
    // //廣播給全域性
    // io.emit(`recvmsg`,data)
  })
})


app.use(cookieParser())
app.use(bodyParser.json())
app.use(`/user`,userRouter)
app.use(function(req,res,next){
  if(req.url.startsWith(`/user/`) || req.url.startsWith(`/static/`)){
    return next()
  }
  const store = createStore(reducers,compose(
    applyMiddleware(thunk)
  ))
  //這個 context 物件包含了渲染的結果
  let context = {}
  const root = (<Provider store={store}>
                    <StaticRouter
                      location={req.url}
                      context={context}
                      >
                        <App></App>
                    </StaticRouter>
                </Provider>)
  const markupStream = renderToNodeStream(root)
  markupStream.pipe(res,{end:false})
  markupStream.on(`end`,()=>{
    res.end()
  })
})
//對映build檔案路徑,專案上要使用
app.use(`/`,express.static(path.resolve(`build`)))


server.listen(8088, function () {
    console.log(`開啟成功`)
})
複製程式碼

此時將服務端renderToNodeStream後的程式碼返回給前端,但是這個時候還是不行,我們執行一下npm run server,可以看到報錯了。

React服務端渲染+pm2自動化部署

css-modules-require-hook/asset-require-hook

css-modules-require-hook

  • 因為服務端此時不認識我們的css檔案,我們需要安裝一個包,來讓服務端處理css檔案。
  • npm i css-modules-require-hook -S安裝在生產環境下。
  • 在專案根目錄建立一個crmh.conf.js鉤子檔案進行配置,看下圖。
React服務端渲染+pm2自動化部署

寫入程式碼

// css-modules-require-hook 
module.exports = {
  generateScopedName: `[name]__[local]___[hash:base64:5]`,
  //下面的程式碼在本專案中暫時用不到,但是以下配置在我另一個專案中有用到,我來講一下他的配置
  //副檔名
  //extensions: [`.scss`,`.css`],
  //鉤子,這裡主要做一些預處理的scss或者less檔案
  //preprocessCss: (data, filename) =>
  //    require(`node-sass`).renderSync({
  //        data,
  //        file: filename
  //    }).css,
  //是否匯出css類名,主要用於CSSModule
  //camelCase: true,
};
複製程式碼
  • 修改我們的server.js檔案,新增import csshook from `css-modules-require-hook/preset`,注意⚠️一定要把這行程式碼放在匯入App模組之前
import csshook from `css-modules-require-hook/preset`
//我們的首頁入口
import App from `../src/App`
複製程式碼

此時在執行server.js,會發現又報了個錯。

React服務端渲染+pm2自動化部署

asset-require-hook

  • 這個錯誤是因為服務端沒有處理前端程式碼需要的圖片
  • 需要安裝npm i asset-require-hook -S,這個外掛用來讓服務端處理圖片,注意⚠️前提是客戶端程式碼,引用圖片都需要require
  • server.js寫入程式碼
//解決圖片問題,客戶端程式碼引用圖片都需要require
import assethook from `asset-require-hook`
assethook({
  extensions:[`png`],
  //圖片大小下於10000的圖片會直接base64編碼
  limit: 10000
})
複製程式碼

執行之後發現又報錯了,這個很簡單,因為我們只有image的引用名字,卻沒有地址

React服務端渲染+pm2自動化部署
  • 所以此時要在外面加個殼,把之前build之後的靜態js、css檔案引入進去,新增html、head這些標籤。來看完整程式碼
import `babel-polyfill`
import express from `express`
import React from `react`
import {renderToString,renderToStaticMarkup,renderToNodeStream} from `react-dom/server`

//引入css檔案和js檔案
import staticPath from `../build/asset-manifest.json`

import thunk from `redux-thunk`;
import { Provider } from `react-redux`;
import {StaticRouter} from `react-router-dom`
import {
  createStore,
  applyMiddleware,
  //組合函式用的
  compose
} from `redux`;
//解決服務端渲染的圖片問題 必須放在App之前
import csshook from `css-modules-require-hook/preset`
//解決圖片問題,需要require
import assethook from `asset-require-hook`
assethook({
  extensions:[`png`],
  limit: 10000
})
import App from `../src/App`
import reducers from `../src/reducer`;

import userRouter from `./routes/user`
import bodyParser from `body-parser`
import cookieParser from `cookie-parser`
import model from `./model`
import path from `path`
import https from `http`
import socketIo from `socket.io`

const Chat = model.getModel(`chat`)
//新建app
const app = express()

//work with express
const server = https.Server(app)
const io = socketIo(server)
io.on(`connection`,function(socket){
  socket.on(`sendmsg`,function(data){
    let {from,to,msg} = data
    let chatid = [from,to].sort().join(`_`)
    Chat.create({chatid,from,to,content:msg},function(e,d){
      io.emit(`recvmsg`,Object.assign({},d._doc))
    })
    // console.log(data)
    // //廣播給全域性
    // io.emit(`recvmsg`,data)
  })
})


app.use(cookieParser())
app.use(bodyParser.json())
app.use(`/user`,userRouter)
app.use(function(req,res,next){
  if(req.url.startsWith(`/user/`) || req.url.startsWith(`/static/`)){
    return next()
  }
  const store = createStore(reducers,compose(
    applyMiddleware(thunk)
  ))
  const obj = {
    `/msg`:`聊天訊息列表`,
    `/me`:`個人中心列表`
  }
  //這個 context 物件包含了渲染的結果
  let context = {}
  res.write(`<!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
      <meta name="theme-color" content="#000000">
      <meta name="description" content="${obj[req.url]}"/>
      <meta name="keywords" content="SSR">
      <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
      <link rel="stylesheet" href="/${staticPath[`main.css`]}">
      <title>React App</title>
    </head>
    <body>
      <noscript>
        You need to enable JavaScript to run this app.
      </noscript>
      <div id="root">`)
  const root = (<Provider store={store}>
                    <StaticRouter
                      location={req.url}
                      context={context}
                      >
                        <App></App>
                    </StaticRouter>
                </Provider>)
  const markupStream = renderToNodeStream(root)
  markupStream.pipe(res,{end:false})
  markupStream.on(`end`,()=>{
    res.write(`</div>
          <script src="/${staticPath[`main.js`]}"></script>
        </body>
      </html>`)
    res.end()
  })
})
//對映build檔案路徑,專案上要使用
app.use(`/`,express.static(path.resolve(`build`)))


server.listen(8088, function () {
    console.log(`開啟成功`)
})
複製程式碼
  • 這個時候我們可以在html標籤里加上SEO的meta<meta name="keywords" content="SSR">
  • 最後還要把客戶端的index.js檔案中的渲染機制改成hydrate,不用render,他們之間的區別可以看這個(傳送門☞render !== hydrate
ReactDOM.hydrate(
    (<Provider store={store}>
        <BrowserRouter>
            <App></App>
        </BrowserRouter>
    </Provider>),
    document.getElementById(`root`)
)
複製程式碼

到此為止我們開發模式下的SSR搭建完畢,接下來生產模式的坑我來講一下。

生產環境SSR準備

我們上面所講的只是開發模式下的SSR,因為我們是通過babel-node編譯jsx和es6程式碼的,只要一脫離babel-node就會全錯,所以我們需要webpack打包服務端程式碼

我們需要建立一個webserver.config.js,用來打包server的程式碼

const path = require(`path`),
    fs = require(`fs`),
    webpack = require(`webpack`),
    autoprefixer = require(`autoprefixer`),
    HtmlWebpackPlugin = require(`html-webpack-plugin`),
    ExtractTextPlugin = require(`extract-text-webpack-plugin`)
    cssFilename = `static/css/[name].[contenthash:8].css`;
    CleanWebpackPlugin = require(`clean-webpack-plugin`);
    nodeExternals = require(`webpack-node-externals`);

serverConfig = {
  context: path.resolve(__dirname, `..`),
  entry: {server: `./server/server`},
  output: {
      libraryTarget: `commonjs2`,
      path: path.resolve(__dirname, `../build/server`),
      filename: `static/js/[name].js`,
      chunkFilename: `static/js/chunk.[name].js`
  },
  // target: `node` 指明構建出的程式碼是要執行在node環境裡.
  // 不把 Node.js 內建的模組打包進輸出檔案中,例如 fs net 模組等
  target: `node`,
  //指定在node環境中是否要這些模組 
  node: {
      __filename: true,
      __dirname: true,
      // module:true
  },
  module: {
      loaders: [{
          test: /.js$/,
          exclude: /node_modules/,
          loader: `babel-loader?cacheDirectory=true`,
          options: {
              presets: [`es2015`, `react-app`, `stage-0`],
              plugins: [`add-module-exports`,
              [
                "import",
                {
                  "libraryName": "antd-mobile",
                  "style": "css"
                }
              ],"transform-decorators-legacy"]
          },
      },{
        test: /.css$/,
        exclude: /node_modules|antd-mobile.css/,            
        loader: ExtractTextPlugin.extract(
          Object.assign(
            {
              fallback: {
                loader: require.resolve(`style-loader`),
                options: {
                  hmr: false,
                },
              },
              use: [
                {
                  loader: require.resolve(`css-loader`),
                  options: {
                    importLoaders: 1,
                    minimize: true,
                    modules: false,
                    localIdentName:"[name]-[local]-[hash:base64:8]",
                    // sourceMap: shouldUseSourceMap,
                  },
                },
                {
                  loader: require.resolve(`postcss-loader`),
                  options: {
                    ident: `postcss`,
                    plugins: () => [
                      require(`postcss-flexbugs-fixes`),
                      autoprefixer({
                        browsers: [
                          `>1%`,
                          `last 4 versions`,
                          `Firefox ESR`,
                          `not ie < 9`, // React doesn`t support IE8 anyway
                        ],
                        flexbox: `no-2009`,
                      }),
                    ],
                  },
                },
              ],
            },
          )
        ),
      },
      {
        test: /.css$/,
        include: /node_modules|antd-mobile.css/,
        use: ExtractTextPlugin.extract({
          fallback: require.resolve(`style-loader`),
          use: [{
            loader: require.resolve(`css-loader`),
            options: {
              modules:false
            },
          }]
        })
      }, {
          test: /.(jpg|png|gif|webp)$/,
          loader: require.resolve(`url-loader`),
            options: {
              limit: 10000,
              name: `static/media/[name].[hash:8].[ext]`,
            },
      }, {
          test: /.json$/,
          loader: `json-loader`,
      }]
  },
  // 不把 node_modules 目錄下的第三方模組打包進輸出檔案中,
  externals: [nodeExternals()],
  resolve: {extensions: [`*`, `.js`, `.json`, `.scss`]},
  plugins: [
      new CleanWebpackPlugin([`../build/server`]),
      new webpack.optimize.OccurrenceOrderPlugin(),
      //把第三方庫從js檔案中分離出來
      new webpack.optimize.CommonsChunkPlugin({
        //抽離相應chunk的共同node_module
        minChunks(module) {
          return /node_modules/.test(module.context);
        },
        //從要抽離的chunk中的子chunk抽離相同的模組
        children: true,
        //是否非同步抽離公共模組,引數boolean||string
        async: false,
      }),
      new webpack.optimize.CommonsChunkPlugin({
        children:true,
        //若引數是string即為抽離出來後的檔名
        async: `shine`,
        //最小打包的檔案模組數,即要抽離的公共模組中的公共數,比如三個chunk只有1個用到就不算公共的            
        //若為Infinity,則會把webpack runtime的程式碼放入其中(webpack 不再自動抽離公共模組)
        minChunks:2
      }),
      //壓縮
      new webpack.optimize.UglifyJsPlugin(),
      //分離css檔案
      new ExtractTextPlugin({
        filename: cssFilename,
      }),
      new webpack.IgnorePlugin(/^./locale$/, /moment$/),
  ],
}

module.exports =  serverConfig
複製程式碼

重點⚠️

  • 指定target,打包出來的程式碼執行在哪裡
  • 指定externals不要把node_modules包打包,因為此專案執行在服務端,直接用外面的node_modules就行。不然打包後會很大。
  • loader中用babel對js的處理

ok,現在來我們改一下package.json的npm scripts,新增一個packServer,順便改一下build的scripts

  "scripts": {
    "clean": "rm -rf build/",
    "dev": "node scripts/start.js",
    "start": "cross-env NODE_ENV=development npm run server & npm run dev",
    "build": "npm run clean && node scripts/build.js && npm run packServer",
    "test": "nodemon scripts/test.js --env=jsdom",
    "server": "cross-env NODE_ENV=test nodemon --exec babel-node server/server.js",
    "gulp": "cross-env NODE_ENV=production gulp",
    "packServer": "cross-env NODE_ENV=production webpack --config ./config/webserver.config.js"
  },
複製程式碼
  • packServer指定了生產環境,這在之後會用到。
  • build是先clean掉build資料夾,在去打包客戶端的程式碼,打包完之後再去打包服務端的程式碼

那麼到這裡為止我們差不多可以自己試試了

  • npm run build,會生成打包後的build資料夾,裡面包含了我們的服務端和客戶端程式碼
  • 找到打包後的node檔案執行它,在build/server/static/js目錄下,可直接node檔案啟動。這就解決了我們生產環境下的問題。

pm2,伺服器自動部署

現在我們要把我們的專案部署到伺服器上,並用pm2守護程式。

React服務端渲染+pm2自動化部署
  • 首先我們得有一臺雲伺服器,這裡我是在阿里雲買的一臺ubuntu 14.04
  • 需要一個已經備案後的域名,域名也可以在阿里雲買。當然也可以不用,可以直接伺服器地址訪問。
  • ok讓我們開始吧。

伺服器部署

  • 在部署到伺服器之前我們程式碼中還有些東西需要修改,修改mongod的連線地址.
const env = process.env.NODE_ENV || `development`
//當生產環境時,需要改變mongodb的連線埠,根據你伺服器的mongodb埠來,我這裡是19999
const BASE_URL = env == `development`?"mongodb://localhost:27017/chat":"mongodb://127.0.0.1:19999/chat";
複製程式碼
  • 修改客戶端socket.io的連結地址const socket = io(`ws://host:port`),改成你自己的伺服器地址和埠號
  • 我們需要將自己的專案上傳至碼雲。這裡我使用碼雲,主要是因為碼雲的私倉是免費的。
  • 我們需要進入伺服器的ssh目錄下複製id_rsa.pub裡的公鑰放在碼雲的ssh公鑰中,可進入設定,具體看圖
React服務端渲染+pm2自動化部署
  • 我們也要把自己電腦上的ssh公鑰在碼雲中設定,我這裡是mac,在自己的使用者目錄下,可以按cmd+shift+.看隱藏檔案(如果你設定過了,這一步就不要了)。
  • 伺服器安裝git,mongodb,pm2,nginx(如果伺服器已經安裝過了,就不需要了)
  • 需要開啟mongodb
  • 我們在專案根目錄新建一個ecosystem.json檔案,這個檔案是pm2的配置檔案,具體的我就不說了,大家如果感興趣可以去官網看看,(傳送門☞pm2官網
{
  "apps": [
    {
      //應用名稱
      "name": "chat",
      //執行檔案的路徑
      "script": "./build/server/static/js/server.js",
      "env": {
        "COMMON_VARIABLE": "true"
      },
      "env_production": {
        "NODE_ENV": "production"
      }
    }
  ],
  "deploy": {
    "production": {
      //伺服器使用者
      "user": "xxx",
      //伺服器地址
      "host": ["xxx"],
      //伺服器埠
      "port": "xxx",
      "ref": "origin/master",
      //這裡填你的專案git ssh
      "repo": "xxx",
      //伺服器的存放專案路徑
      "path": "/www/chat/production",
      "ssh_options": "StrictHostKeyChecking=no",
      //鉤子
      "post-deploy": "npm --registry https://registry.npm.taobao.org install && npm run build && pm2 startOrRestart ecosystem.json --env production",
      "env": {
        //環境
        "NODE_ENV": "production"
      }
    }
  }
}
複製程式碼
  • 在伺服器新建專案目錄新建/www/chat/資料夾。
  • 在本地電腦執行 pm2 deploy ecosystem.json production setup
  • 這裡大家肯定會報錯,這是我故意埋的坑,因為chat資料夾的許可權不夠,需要進入伺服器的www資料夾,執行sudo chmod 777 chat
  • 進入伺服器的.bashrc檔案,注視掉上面的幾行程式碼
  • source .bashrc重新載入一下.bashrc檔案
  • 開啟pm2服務 pm2 deploy ecosystem.json production
  • 這裡可能有的人會報錯,主要原因是本地電腦的pm2的許可權問題,需要找到pm2資料夾,chmod 666 pm2
  • 如果上述問題都解決了最後會如圖所示
React服務端渲染+pm2自動化部署
  • 最後我們可以進入伺服器,pm2 list,看到成功跑起來了
React服務端渲染+pm2自動化部署
  • 如果應用在不斷的重啟,說明開啟失敗了,需要pm2 logs看看日誌
React服務端渲染+pm2自動化部署
  • 我們可以訪問伺服器地址:8088,並看到應用跑起來了

域名代理

  • 我們進入阿里雲控制檯解析自己的域名(傳送門☞阿里雲
React服務端渲染+pm2自動化部署
  • 新增一條記錄
React服務端渲染+pm2自動化部署
  • 回到伺服器,我們修改nginx配置檔案,通過反向代理,讓我們通過域名也可以訪問他
upstream chat {
  server 127.0.0.1:8088;
}

server {
  listen 80;
  server_name www.webman.vip;

  location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header X-Nginx-Proxy true;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";

    proxy_pass http://chat;
    proxy_redirect off;
  }
  # 靜態檔案地址
  location ~* ^.+.(jpg|jpeg|gif|png|ico|css|js|pdf|txt){
    root /www/website/production/current/build;
  }
}
複製程式碼
  • 在伺服器執行sudo nginx -s reload,重啟nginx。此時我們就可以通過我們的域名地址訪問到我們的應用了。

  • 這裡可能訪問會404,這個時候我們需要看一下我們伺服器的防火牆,sudo vi /etc/iptables.up.rules,修改mongodb的對外埠,並且重啟防火牆sudo iptables-restore < /etc/iptables.up.rules

-A INPUT -s 127.0.0.1 -p tcp --destination-port 8088 -m state --state NEW,ESTABLISHED -j ACCEPT
-A OUTPUT -d 127.0.0.1 -p tcp --source-port 8088 -m state --state ESTABLISHED -j ACCEPT
複製程式碼
  • 檢視阿里雲控制檯的安全組是否開了對應的埠
React服務端渲染+pm2自動化部署
  • 最後最後!!!,終於成功了。可以點選連結檢視一下。 走你!

  • 當然下次如果你想直接更新專案,可以在專案對應的路徑提交到git上,然後再使用pm2 deploy ecosystem.json production即可在伺服器上自動部署

相關文章