サーバーサイドレンダリング(SSR)は、 React Angle 2 + 、および Vue2の最大の強みの1つとして長い間宣伝されてきたものの1つです。 これにより、サーバー上でアプリをレンダリングし、ページの読み込み後にクライアント側の反応性でアプリをハイドレイトして、応答性を大幅に向上させ、ページの読み込み時間を改善できます。

残念ながら、設定するのは最も明白なことではなく、サーバー上でVue.jsアプリをレンダリングするためのドキュメントはいくつかの場所に分散しています。 うまくいけば、このガイドがあなたのために物事を片付けるのに役立つはずです。 🙂

インストール

vue-cliのwebpack-simple テンプレートから始めて、作業の共通ベースを提供します。

# Create the project
$ vue init webpack-simple vue-ssr-example
$ cd vue-ssr-example

# Install dependencies
$ yarn # (or npm install)

また、サーバー用の express vue-ssr-webpack-pluginによって生成されるバンドルをレンダリングするためのvue-server-rendererの3つのパッケージも必要です。

# Install with yarn ...
$ yarn add express vue-server-renderer
$ yarn add vue-ssr-webpack-plugin -D # Add this as a development dependency as we don't need it in production.

# ... or with NPM
$ npm install express vue-server-renderer
$ npm install vue-ssr-webpack-plugin -D

アプリの準備

webpack-simple テンプレートには、すぐに使用できるSSR機能は付属していません。 最初に構成する必要があることがいくつかあります。

最初に行うことは、サーバー用に別のエントリファイルを作成することです。 現在、クライアントエントリはmain.jsにあります。 それをコピーして、そこからmain.server.jsを作成しましょう。 変更はかなり簡単です。 el 参照を削除して、デフォルトのエクスポートでアプリを返す必要があります。

src / main.server.js
import Vue from 'vue';
import App from './App.vue';

// Receives the context of the render call, returning a Promise resolution to the root Vue instance.
export default context => {
  return Promise.resolve(
    new Vue({
      render: h => h(App)
    })
  );
}

また、SSRに備えるために、index.htmlを少し変更する必要があります。

交換

、 そのようです:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>vue-ssr-example</title>
  </head>
  <body>
    <!--vue-ssr-outlet-->
    <script src="/dist/build.js"></script>
  </body>
</html>

Webpackの構成

ここで、サーバーバンドルをレンダリングするための個別のWebpack構成ファイルが必要です。 webpack.config.jsを新しいファイルwebpack.server.config.jsにコピーします。

行う必要のある変更がいくつかあります。

webpack.server.config.js
const path = require('path')
const webpack = require('webpack')
// Load the Vue SSR plugin. Don't forget this. :P
const VueSSRPlugin = require('vue-ssr-webpack-plugin')

module.exports = {
  // The target should be set to "node" to avoid packaging built-ins.
  target: 'node',
  // The entry should be our server entry file, not the default one.
  entry: './src/main.server.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    publicPath: '/dist/',
    filename: 'build.js',
    // Outputs node-compatible modules instead of browser-compatible.
    libraryTarget: 'commonjs2'
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
          }
          // other vue-loader options go here
        }
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      },
      {
        test: /\.(png|jpg|gif|svg)$/,
        loader: 'file-loader',
        options: {
          name: '[name].[ext]?[hash]'
        }
      }
    ]
  },
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js'
    }
  },
  // We can remove the devServer block.
  performance: {
    hints: false
  },
  // Avoids bundling external dependencies, so node can load them directly from node_modules/
  externals: Object.keys(require('./package.json').dependencies),
  devtool: 'source-map',
  // No need to put these behind a production env variable.
  plugins: [
    // Add the SSR plugin here.
    new VueSSRPlugin(),
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"production"'
      }
    }),
    new webpack.optimize.UglifyJsPlugin({
      sourceMap: true,
      compress: {
        warnings: false
      }
    }),
    new webpack.LoaderOptionsPlugin({
      minimize: true
    })
  ]
}

構成の構築

開発を簡素化するために、 package.json のビルドスクリプトを更新して、クライアントとサーバーの両方のWebpackバンドルをビルドしましょう。

単一のbuildスクリプトをこれらの3つに置き換えます。 使用法は同じですが、 build:clientbuild:server をそれぞれ使用して、クライアントバンドルまたはサーバーバンドルを個別にビルドできるようになりました。

package.json(部分的)
{
  ...
  "scripts": {
    ...
    "build": "npm run build:server && npm run build:client",
    "build:client": "cross-env NODE_ENV=production webpack --progress --hide-modules",
    "build:server": "cross-env NODE_ENV=production webpack --config webpack.server.config.js --progress --hide-modules"
  },
  ...
}

サーバースクリプト

ここで、アプリケーションをレンダリングするためのサーバースクリプトが必要です。

server.js(部分的)
#!/usr/bin/env node

const fs = require('fs');
const express = require('express');
const { createBundleRenderer } = require('vue-server-renderer');

const bundleRenderer = createBundleRenderer(
  // Load the SSR bundle with require.
  require('./dist/vue-ssr-bundle.json'),
  {
    // Yes, I know, readFileSync is bad practice. It's just shorter to read here.
    template: fs.readFileSync('./index.html', 'utf-8')
  }
);

// Create the express app.
const app = express();

// Serve static assets from ./dist on the /dist route.
app.use('/dist', express.static('dist'));

// Render all other routes with the bundleRenderer.
app.get('*', (req, res) => {
  bundleRenderer
    // Renders directly to the response stream.
    // The argument is passed as "context" to main.server.js in the SSR bundle.
    .renderToStream({url: req.path})
    .pipe(res);
});

// Bind the app to this port.
app.listen(8080);

アプリの実行

すべてがうまくいけば、バンドルを構築し、次のコマンドでサーバーを実行できるはずです。

# Build client and server bundles.
$ npm run build
# Run the HTTP server.
$ node ./server.js

http:// localhost:8080 にアクセスすると、すべてが同じように見えるはずです。 ただし、JavaScriptを無効にしても、アプリが最初にサーバー上でレンダリングされるため、すべてが同じように見えます。


警告

  • バンドルではなくnode_modulesからロードされたモジュールは、リクエスト間で変更できません(つまり、グローバル状態になります)。そうしないと、アプリケーションのレンダリング時に一貫性のない結果が得られます。
  • テーブルを正しく記述していることを確認してください(theadおよび/またはtbodyラッパー要素を含めます)。クライアント側バージョンはこれらの問題を検出できますが、サーバー側バージョンは検出できません。水分補給の不一致を引き起こす可能性があります。

ボーナスラウンド

  • クライアントとサーバーのどちらでレンダリングされたかによって、アプリの表示を変えてみてください。 ヒント:ルートレンダリング関数からアプリに小道具を渡すことができます。
  • Vuexの状態をサーバーからクライアントに同期します。 それはいくつかのグローバル変数を含むかもしれません!

参照