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


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

15315912157248fdea6bb73.jpeg

服務端渲染

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

為什么要用SSR?

首先我們需要知道SSR對于SPA的好處,優(yōu)勢是什么。

  • 更好的SEO(Search Engine Optimization),SEO是搜索引擎優(yōu)化,簡而言之就是針對百度這些搜索引擎,可以讓他們搜索到我們的應用。這里可能會有誤區(qū),就是我也可以在index.html上寫SEO,為什么會不起作用。因為React、Vue的原理是客戶端渲染,通過瀏覽器去加載js、css,有一個時間上的延遲,而搜索引擎不會管你的延遲,他就覺得你如果沒加載出來就是沒有的,所以是搜不到的。
  • 解決一開始的白屏渲染,上面講了React的渲染原理,而SSR服務端渲染是通過服務端請求數(shù)據(jù),因為服務端內(nèi)網(wǎng)的請求快,性能好所以會更快的加載所有的文件,最后把下載渲染后的頁面返回給客戶端。

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

客戶端渲染路線:

  1. 請求一個html
  2. 服務端返回一個html
  3. 瀏覽器下載html里面的js/css文件
  4. 等待js文件下載完成
  5. 等待js加載并初始化完成
  6. js代碼終于可以運行,由js代碼向后端請求數(shù)據(jù)( ajax/fetch )
  7. 等待后端數(shù)據(jù)返回
  8. react-dom( 客戶端 )從無到完整地,把數(shù)據(jù)渲染為響應頁面

服務端渲染路線:

  1. 請求一個html
  2. 服務端請求數(shù)據(jù)( 內(nèi)網(wǎng)請求快 )
  3. 服務器初始渲染(服務端性能好,較快)
  4. 服務端返回已經(jīng)有正確內(nèi)容的頁面
  5. 客戶端請求js/css文件
  6. 等待js文件下載完成
  7. 等待js加載并初始化完成
  8. react-dom( 客戶端 )把剩下一部分渲染完成( 內(nèi)容小,渲染快 )

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

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

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

這里項目起步是已經(jīng)做完前端和后端,是把已經(jīng)寫好的React Demo直接拿來用

服務端渲染開始

既然是首頁SSR,首先我們要把首頁對應的index.js抽離出來放入我們服務端對應的server.js,那么index.js中組件對應的靜態(tài)css和js文件我們需要打包出來。

用webpack打包文件到build文件夾

我們來運行npm run build

image

我們可以看到兩個重要的文件夾,一個是js文件夾,一個是css文件夾,他就是我們項目的js和css靜態(tài)資源文件

將打包后的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代碼,所以我們在開發(fā)環(huán)境中需要用到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環(huán)境變量的插件。
  • nodemon和supervisor一樣是watch服務端文件,只要一改變就會重新運行,相當于熱重載。nodemon更輕量
  • 最后我們來跑一下npm run server,就能看到服務端跑起來了。
image

ReactDOMServer.renderToString/ReactDOMServer.renderToNodeStream

  • 這里我們先講一下在瀏覽器中,React.createElement把React的類進行實例化,實例化后的組件可以進行mount,最后通過React.render渲染到我們的客戶端瀏覽器界面。
  • 而在服務器中我們可以通過 renderToString或者renderToNodeStream方法把React實例化的組件,直接渲染生成html標簽。那么這倆個有什么區(qū)別呢?
  • renderToNodeStream是React 16最新發(fā)布的東西,它支持直接渲染到節(jié)點流。渲染到流可以減少你的內(nèi)容的第一個字節(jié)(TTFB)的時間,在文檔的下一部分生成之前,將文檔的開頭至結尾發(fā)送到瀏覽器。 當內(nèi)容從服務器流式傳輸時,瀏覽器將開始解析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,
  //組合函數(shù)用的
  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后的代碼返回給前端,但是這個時候還是不行,我們執(zhí)行一下npm run server,可以看到報錯了。

image

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

css-modules-require-hook

  • 因為服務端此時不認識我們的css文件,我們需要安裝一個包,來讓服務端處理css文件。
  • npm i css-modules-require-hook -S安裝在生產(chǎn)環(huán)境下。
  • 在項目根目錄創(chuàng)建一個crmh.conf.js鉤子文件進行配置,看下圖。
image

寫入代碼

// 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,會發(fā)現(xiàn)又報了個錯。

image

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
})

運行之后發(fā)現(xiàn)又報錯了,這個很簡單,因為我們只有image的引用名字,卻沒有地址

image
  • 所以此時要在外面加個殼,把之前build之后的靜態(tài)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,
  //組合函數(shù)用的
  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,他們之間的區(qū)別可以看這個(傳送門?render !== hydrate
ReactDOM.hydrate(
    (<Provider store={store}>
        <BrowserRouter>
            <App></App>
        </BrowserRouter>
    </Provider>),
    document.getElementById('root')
)

到此為止我們開發(fā)模式下的SSR搭建完畢,接下來生產(chǎn)模式的坑我來講一下。

生產(chǎn)環(huán)境SSR準備

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

我們需要創(chuàng)建一個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環(huán)境里.
  // 不把 Node.js 內(nèi)置的模塊打包進輸出文件中,例如 fs net 模塊等
  target: 'node',
  //指定在node環(huán)境中是否要這些模塊 
  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,
        //是否異步抽離公共模塊,參數(shù)boolean||string
        async: false,
      }),
      new webpack.optimize.CommonsChunkPlugin({
        children:true,
        //若參數(shù)是string即為抽離出來后的文件名
        async: 'shine',
        //最小打包的文件模塊數(shù),即要抽離的公共模塊中的公共數(shù),比如三個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,現(xiàn)在來我們改一下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指定了生產(chǎn)環(huán)境,這在之后會用到。
  • build是先clean掉build文件夾,在去打包客戶端的代碼,打包完之后再去打包服務端的代碼

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

  • npm run build,會生成打包后的build文件夾,里面包含了我們的服務端和客戶端代碼
  • 找到打包后的node文件運行它,在build/server/static/js目錄下,可直接node文件啟動。這就解決了我們生產(chǎn)環(huán)境下的問題。

pm2,服務器自動部署

現(xiàn)在我們要把我們的項目部署到服務器上,并用pm2守護進程。

image
  • 首先我們得有一臺云服務器,這里我是在阿里云買的一臺ubuntu 14.04
  • 需要一個已經(jīng)備案后的域名,域名也可以在阿里云買。當然也可以不用,可以直接服務器地址訪問。
  • ok讓我們開始吧。

服務器部署

  • 在部署到服務器之前我們代碼中還有些東西需要修改,修改mongod的連接地址.
const env = process.env.NODE_ENV || 'development'
//當生產(chǎn)環(huán)境時,需要改變mongodb的連接端口,根據(jù)你服務器的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公鑰中,可進入設置,具體看圖
image
  • 我們也要把自己電腦上的ssh公鑰在碼云中設置,我這里是mac,在自己的用戶目錄下,可以按cmd+shift+.看隱藏文件(如果你設置過了,這一步就不要了)。
  • 服務器安裝git,mongodb,pm2,nginx(如果服務器已經(jīng)安裝過了,就不需要了)
  • 需要開啟mongodb
  • 我們在項目根目錄新建一個ecosystem.json文件,這個文件是pm2的配置文件,具體的我就不說了,大家如果感興趣可以去官網(wǎng)看看,(傳送門?pm2官網(wǎng)
{
  "apps": [
    {
      //應用名稱
      "name": "chat",
      //執(zhí)行文件的路徑
      "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": {
        //環(huán)境
        "NODE_ENV": "production"
      }
    }
  }
}
  • 在服務器新建項目目錄新建/www/chat/文件夾。
  • 在本地電腦執(zhí)行 pm2 deploy ecosystem.json production setup
  • 這里大家肯定會報錯,這是我故意埋的坑,因為chat文件夾的權限不夠,需要進入服務器的www文件夾,執(zhí)行sudo chmod 777 chat。
  • 進入服務器的.bashrc文件,注視掉上面的幾行代碼
  • source .bashrc重新載入一下.bashrc文件
  • 開啟pm2服務 pm2 deploy ecosystem.json production
  • 這里可能有的人會報錯,主要原因是本地電腦的pm2的權限問題,需要找到pm2文件夾,chmod 666 pm2
  • 如果上述問題都解決了最后會如圖所示
image
  • 最后我們可以進入服務器,pm2 list,看到成功跑起來了
image
  • 如果應用在不斷的重啟,說明開啟失敗了,需要pm2 logs看看日志
image
  • 我們可以訪問服務器地址:8088,并看到應用跑起來了

域名代理

  • 我們進入阿里云控制臺解析自己的域名(傳送門?阿里云
image
  • 添加一條記錄
image
  • 回到服務器,我們修改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;
  }
  # 靜態(tài)文件地址
  location ~* ^.+\.(jpg|jpeg|gif|png|ico|css|js|pdf|txt){
    root /www/website/production/current/build;
  }
}
  • 在服務器執(zhí)行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
  • 查看阿里云控制臺的安全組是否開了對應的端口
image
  • 最后最后?。?!,終于成功了??梢渣c擊鏈接查看一下。 走你!

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

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容