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

服務(wù)端渲染
Server Slide Rendering服務(wù)端渲染,又簡寫為SSR,他一般被用在我們的SPA(Single-Page Application),即單頁應(yīng)用。
為什么要用SSR?
首先我們需要知道SSR對(duì)于SPA的好處,優(yōu)勢是什么。
- 更好的
SEO(Search Engine Optimization),SEO是搜索引擎優(yōu)化,簡而言之就是針對(duì)百度這些搜索引擎,可以讓他們搜索到我們的應(yīng)用。這里可能會(huì)有誤區(qū),就是我也可以在index.html上寫SEO,為什么會(huì)不起作用。因?yàn)镽eact、Vue的原理是客戶端渲染,通過瀏覽器去加載js、css,有一個(gè)時(shí)間上的延遲,而搜索引擎不會(huì)管你的延遲,他就覺得你如果沒加載出來就是沒有的,所以是搜不到的。 - 解決一開始的
白屏渲染,上面講了React的渲染原理,而SSR服務(wù)端渲染是通過服務(wù)端請求數(shù)據(jù),因?yàn)榉?wù)端內(nèi)網(wǎng)的請求快,性能好所以會(huì)更快的加載所有的文件,最后把下載渲染后的頁面返回給客戶端。
上面提到了服務(wù)端渲染和客戶端渲染,那么它們的區(qū)別是什么呢?
客戶端渲染路線:
- 請求一個(gè)html
- 服務(wù)端返回一個(gè)html
- 瀏覽器下載html里面的js/css文件
- 等待js文件下載完成
- 等待js加載并初始化完成
- js代碼終于可以運(yùn)行,由js代碼向后端請求數(shù)據(jù)( ajax/fetch )
- 等待后端數(shù)據(jù)返回
- react-dom( 客戶端 )從無到完整地,把數(shù)據(jù)渲染為響應(yīng)頁面
服務(wù)端渲染路線:
- 請求一個(gè)html
- 服務(wù)端請求數(shù)據(jù)( 內(nèi)網(wǎng)請求快 )
- 服務(wù)器初始渲染(服務(wù)端性能好,較快)
- 服務(wù)端返回已經(jīng)有正確內(nèi)容的頁面
- 客戶端請求js/css文件
- 等待js文件下載完成
- 等待js加載并初始化完成
- react-dom( 客戶端 )把剩下一部分渲染完成( 內(nèi)容小,渲染快 )
其主要區(qū)別就在于,客戶端從
無到有的渲染,服務(wù)端是先在服務(wù)端渲染一部分,在再客戶端渲染一小部分。
我們怎么去做服務(wù)端渲染?
我們這里是用express框架,node做中間層進(jìn)行服務(wù)端渲染。通過將首頁進(jìn)行同構(gòu)處理,讓服務(wù)端,通過調(diào)用ReactDOMServer.renderToNodeStream方法把Virtual DOM轉(zhuǎn)換成HTML字符串返回給客戶端,從而達(dá)到服務(wù)端渲染的目的。
這里項(xiàng)目起步是已經(jīng)做完前端和后端,是把已經(jīng)寫好的React Demo直接拿來用
服務(wù)端渲染開始
既然是首頁SSR,首先我們要把首頁對(duì)應(yīng)的index.js抽離出來放入我們服務(wù)端對(duì)應(yīng)的server.js,那么index.js中組件對(duì)應(yīng)的靜態(tài)css和js文件我們需要打包出來。
用webpack打包文件到build文件夾
我們來運(yùn)行npm run build
我們可以看到兩個(gè)重要的文件夾,一個(gè)是js文件夾,一個(gè)是css文件夾,他就是我們項(xiàng)目的js和css靜態(tài)資源文件
將打包后的build文件能在服務(wù)端server.js中訪問到
因?yàn)槭欠?wù)端,我們需要用到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文件路徑,項(xiàng)目上要使用
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'))這兩段代碼。 - 他們把打包后的主頁放入服務(wù)端代碼中返回給客戶端。
- 因?yàn)樯厦嫖矣昧?code>import代碼,所以我們在開發(fā)環(huán)境中需要用到
babel-cli里的babel-node來編譯。 - 安裝
npm --registry https://registry.npm.taobao.orgi babel-cli -S`,大家如果覺得這樣切換源麻煩,可以下個(gè)nrm,360度無死角切換各種源,好用! - 我們需要修改
package.json的啟動(dòng)服務(wù)器的npm scripts。"server": "NODE_ENV=test nodemon --exec babel-node server/server.js" -
cross-env跨平臺(tái)設(shè)置node環(huán)境變量的插件。 - nodemon和supervisor一樣是watch服務(wù)端文件,只要一改變就會(huì)重新運(yùn)行,相當(dāng)于
熱重載。nodemon更輕量 - 最后我們來跑一下
npm run server,就能看到服務(wù)端跑起來了。
ReactDOMServer.renderToString/ReactDOMServer.renderToNodeStream
- 這里我們先講一下在
瀏覽器中,React.createElement把React的類進(jìn)行實(shí)例化,實(shí)例化后的組件可以進(jìn)行mount,最后通過React.render渲染到我們的客戶端瀏覽器界面。 - 而在服務(wù)器中我們可以通過
renderToString或者renderToNodeStream方法把React實(shí)例化的組件,直接渲染生成html標(biāo)簽。那么這倆個(gè)有什么區(qū)別呢? -
renderToNodeStream是React 16最新發(fā)布的東西,它支持直接渲染到節(jié)點(diǎn)流。渲染到流可以減少你的內(nèi)容的第一個(gè)字節(jié)(TTFB)的時(shí)間,在文檔的下一部分生成之前,將文檔的開頭至結(jié)尾發(fā)送到瀏覽器。 當(dāng)內(nèi)容從服務(wù)器流式傳輸時(shí),瀏覽器將開始解析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)
))
//這個(gè) context 對(duì)象包含了渲染的結(jié)果
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文件路徑,項(xiàng)目上要使用
app.use('/',express.static(path.resolve('build')))
server.listen(8088, function () {
console.log('開啟成功')
})
此時(shí)將服務(wù)端renderToNodeStream后的代碼返回給前端,但是這個(gè)時(shí)候還是不行,我們執(zhí)行一下npm run server,可以看到報(bào)錯(cuò)了。
css-modules-require-hook/asset-require-hook
css-modules-require-hook
- 因?yàn)榉?wù)端此時(shí)
不認(rèn)識(shí)我們的css文件,我們需要安裝一個(gè)包,來讓服務(wù)端處理css文件。 -
npm i css-modules-require-hook -S安裝在生產(chǎn)環(huán)境下。 - 在項(xiàng)目根目錄創(chuàng)建一個(gè)
crmh.conf.js鉤子文件進(jìn)行配置,看下圖。
寫入代碼
// css-modules-require-hook
module.exports = {
generateScopedName: '[name]__[local]___[hash:base64:5]',
//下面的代碼在本項(xiàng)目中暫時(shí)用不到,但是以下配置在我另一個(gè)項(xiàng)目中有用到,我來講一下他的配置
//擴(kuò)展名
//extensions: ['.scss','.css'],
//鉤子,這里主要做一些預(yù)處理的scss或者less文件
//preprocessCss: (data, filename) =>
// require('node-sass').renderSync({
// data,
// file: filename
// }).css,
//是否導(dǎo)出css類名,主要用于CSSModule
//camelCase: true,
};
- 修改我們的
server.js文件,添加import csshook from 'css-modules-require-hook/preset',注意??,一定要把這行代碼放在導(dǎo)入App模塊之前。
import csshook from 'css-modules-require-hook/preset'
//我們的首頁入口
import App from '../src/App'
此時(shí)在運(yùn)行server.js,會(huì)發(fā)現(xiàn)又報(bào)了個(gè)錯(cuò)。
asset-require-hook
- 這個(gè)錯(cuò)誤是因?yàn)榉?wù)端沒有處理前端代碼需要的圖片
- 需要安裝
npm i asset-require-hook -S,這個(gè)插件用來讓服務(wù)端處理圖片,注意??,前提是客戶端代碼,引用圖片都需要require - 在
server.js寫入代碼
//解決圖片問題,客戶端代碼引用圖片都需要require
import assethook from 'asset-require-hook'
assethook({
extensions:['png'],
//圖片大小下于10000的圖片會(huì)直接base64編碼
limit: 10000
})
運(yùn)行之后發(fā)現(xiàn)又報(bào)錯(cuò)了,這個(gè)很簡單,因?yàn)槲覀冎挥衖mage的引用名字,卻沒有地址
- 所以此時(shí)要在外面加個(gè)殼,把之前build之后的
靜態(tài)js、css文件引入進(jìn)去,添加html、head這些標(biāo)簽。來看完整代碼
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';
//解決服務(wù)端渲染的圖片問題 必須放在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':'個(gè)人中心列表'
}
//這個(gè) context 對(duì)象包含了渲染的結(jié)果
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文件路徑,項(xiàng)目上要使用
app.use('/',express.static(path.resolve('build')))
server.listen(8088, function () {
console.log('開啟成功')
})
- 這個(gè)時(shí)候我們可以在html標(biāo)簽里加上SEO的meta
<meta name="keywords" content="SSR"> - 最后還要把客戶端的
index.js文件中的渲染機(jī)制改成hydrate,不用render,他們之間的區(qū)別可以看這個(gè)(傳送門?render !== hydrate)
ReactDOM.hydrate(
(<Provider store={store}>
<BrowserRouter>
<App></App>
</BrowserRouter>
</Provider>),
document.getElementById('root')
)
到此為止我們開發(fā)模式下的SSR搭建完畢,接下來生產(chǎn)模式的坑我來講一下。
生產(chǎn)環(huán)境SSR準(zhǔn)備
我們上面所講的只是開發(fā)模式下的SSR,因?yàn)槲覀兪峭ㄟ^
babel-node編譯jsx和es6代碼的,只要一脫離babel-node就會(huì)全錯(cuò),所以我們需要webpack打包服務(wù)端代碼
我們需要?jiǎng)?chuàng)建一個(gè)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' 指明構(gòu)建出的代碼是要運(yùn)行在node環(huán)境里.
// 不把 Node.js 內(nèi)置的模塊打包進(jìn)輸出文件中,例如 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 目錄下的第三方模塊打包進(jìn)輸出文件中,
externals: [nodeExternals()],
resolve: {extensions: ['*', '.js', '.json', '.scss']},
plugins: [
new CleanWebpackPlugin(['../build/server']),
new webpack.optimize.OccurrenceOrderPlugin(),
//把第三方庫從js文件中分離出來
new webpack.optimize.CommonsChunkPlugin({
//抽離相應(yīng)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ù),比如三個(gè)chunk只有1個(gè)用到就不算公共的
//若為Infinity,則會(huì)把webpack runtime的代碼放入其中(webpack 不再自動(dòng)抽離公共模塊)
minChunks:2
}),
//壓縮
new webpack.optimize.UglifyJsPlugin(),
//分離css文件
new ExtractTextPlugin({
filename: cssFilename,
}),
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
],
}
module.exports = serverConfig
重點(diǎn)??
- 指定target,打包出來的代碼運(yùn)行在哪里
- 指定externals不要把
node_modules包打包,因?yàn)榇隧?xiàng)目運(yùn)行在服務(wù)端,直接用外面的node_modules就行。不然打包后會(huì)很大。 - loader中用babel對(duì)js的處理
ok,現(xiàn)在來我們改一下package.json的
npm scripts,添加一個(gè)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)境,這在之后會(huì)用到。 -
build是先clean掉build文件夾,在去打包客戶端的代碼,打包完之后再去打包服務(wù)端的代碼
那么到這里為止我們差不多可以自己試試了
- 先
npm run build,會(huì)生成打包后的build文件夾,里面包含了我們的服務(wù)端和客戶端代碼 - 找到打包后的node文件運(yùn)行它,在
build/server/static/js目錄下,可直接node文件啟動(dòng)。這就解決了我們生產(chǎn)環(huán)境下的問題。
pm2,服務(wù)器自動(dòng)部署
現(xiàn)在我們要把我們的項(xiàng)目部署到服務(wù)器上,并用pm2守護(hù)進(jìn)程。
- 首先我們得有一臺(tái)云服務(wù)器,這里我是在阿里云買的一臺(tái)
ubuntu 14.04 - 需要一個(gè)已經(jīng)備案后的域名,域名也可以在阿里云買。當(dāng)然也可以不用,可以直接服務(wù)器地址訪問。
- ok讓我們開始吧。
服務(wù)器部署
- 在部署到服務(wù)器之前我們代碼中還有些東西需要修改,修改mongod的連接地址.
const env = process.env.NODE_ENV || 'development'
//當(dāng)生產(chǎn)環(huán)境時(shí),需要改變mongodb的連接端口,根據(jù)你服務(wù)器的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'),改成你自己的服務(wù)器地址和端口號(hào) - 我們需要將自己的項(xiàng)目上傳至碼云。這里我使用碼云,主要是因?yàn)?a target="_blank" rel="nofollow">碼云的私倉是免費(fèi)的。
- 我們需要進(jìn)入服務(wù)器的
ssh目錄下復(fù)制id_rsa.pub里的公鑰放在碼云的ssh公鑰中,可進(jìn)入設(shè)置,具體看圖
- 我們也要把自己電腦上的
ssh公鑰在碼云中設(shè)置,我這里是mac,在自己的用戶目錄下,可以按cmd+shift+.看隱藏文件(如果你設(shè)置過了,這一步就不要了)。 - 服務(wù)器安裝git,mongodb,pm2,nginx
(如果服務(wù)器已經(jīng)安裝過了,就不需要了) - 需要開啟mongodb
- 我們在項(xiàng)目根目錄新建一個(gè)
ecosystem.json文件,這個(gè)文件是pm2的配置文件,具體的我就不說了,大家如果感興趣可以去官網(wǎng)看看,(傳送門?pm2官網(wǎng))
{
"apps": [
{
//應(yīng)用名稱
"name": "chat",
//執(zhí)行文件的路徑
"script": "./build/server/static/js/server.js",
"env": {
"COMMON_VARIABLE": "true"
},
"env_production": {
"NODE_ENV": "production"
}
}
],
"deploy": {
"production": {
//服務(wù)器用戶
"user": "xxx",
//服務(wù)器地址
"host": ["xxx"],
//服務(wù)器端口
"port": "xxx",
"ref": "origin/master",
//這里填你的項(xiàng)目git ssh
"repo": "xxx",
//服務(wù)器的存放項(xiàng)目路徑
"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"
}
}
}
}
- 在服務(wù)器新建項(xiàng)目目錄新建
/www/chat/文件夾。 - 在本地電腦執(zhí)行
pm2 deploy ecosystem.json production setup - 這里大家肯定會(huì)報(bào)錯(cuò),這是我故意埋的坑,因?yàn)?code>chat文件夾的權(quán)限不夠,需要進(jìn)入服務(wù)器的
www文件夾,執(zhí)行sudo chmod 777 chat。 - 進(jìn)入服務(wù)器的.bashrc文件,注視掉上面的幾行代碼
-
source .bashrc重新載入一下.bashrc文件 - 開啟pm2服務(wù) pm2 deploy ecosystem.json production
- 這里可能有的人會(huì)報(bào)錯(cuò),主要原因是本地電腦的pm2的權(quán)限問題,需要找到pm2文件夾,
chmod 666 pm2 - 如果上述問題都解決了最后會(huì)如圖所示
- 最后我們可以進(jìn)入服務(wù)器,
pm2 list,看到成功跑起來了
- 如果應(yīng)用在不斷的
重啟,說明開啟失敗了,需要pm2 logs看看日志
- 我們可以訪問
服務(wù)器地址:8088,并看到應(yīng)用跑起來了
域名代理
- 我們進(jìn)入阿里云控制臺(tái)解析自己的域名(傳送門?阿里云)
- 添加一條記錄
- 回到服務(wù)器,我們修改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;
}
}
在服務(wù)器執(zhí)行
sudo nginx -s reload,重啟nginx。此時(shí)我們就可以通過我們的域名地址訪問到我們的應(yīng)用了。這里可能訪問會(huì)
404,這個(gè)時(shí)候我們需要看一下我們服務(wù)器的防火墻,sudo vi /etc/iptables.up.rules,修改mongodb的對(duì)外端口,并且重啟防火墻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
- 查看阿里云控制臺(tái)的安全組是否開了對(duì)應(yīng)的端口
最后最后?。?!,終于成功了。可以點(diǎn)擊鏈接查看一下。 走你!
當(dāng)然下次如果你想直接更新項(xiàng)目,可以在項(xiàng)目對(duì)應(yīng)的路徑提交到
git上,然后再使用pm2 deploy ecosystem.json production即可在服務(wù)器上自動(dòng)部署。