35
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Vue.jsでサーバサイドレンダリングしたい

Posted at

前置き

今作っているシステムは社内ツールとして作っているのですが、将来的に弊社のクライアント向けに公開するみたいな話もあり、
古いJS・低速なネット環境も想定されるので、それを解決する手段の1つとしてSSR(サーバサイドレンダリング)を勉強します。

https://jp.vuejs.org/v2/guide/ssr.html
こちらのVue公式ドキュメントを参考に作ってます。

http://qiita.com/koki_cheese/items/ce5f16111e8ca251838d
Webpack周りはこちらが参考になりました。

SSRされていない状態

以下は画面に文字列を表示するだけのコードです。

index.js
import Vue from 'vue'
import App from './App'

new Vue({
  render: h => h(App)
}).$mount('#app')
App.vue
<template>
  <div id="app">
    <span>sample ssr!!</span>
    <!-- 本来の実装では以下を設定している
    <router-view></router-view>
    -->
  </div>
</template>
index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>sample ssr</title>
  </head>
  <body>
    <div id="app" />
    <script src="/bundle.js"></script>
  </body>
</html>

Webpack等でindex.jsをエントリーポイントとしてビルドして、bundle.jsを作ります。
ビルド周りの設定は省略しますが、これを画面で表示させると「sample ssr!!」という文字だけが表示されます。

HTMLソースを見てみると
 2017-03-30 16.15.21.png

SSRをしていないので、<div id="app" />のように空のHTMLになっています。
現状は、上記のサンプルコードはコメントアウトしていますが、vue-routerを使ったSPA(シングルページアプリケーション)として動作させています。

これを初期表示時にSSRしてあげるようにコードを改修していきます。

SSR用に変更

index.jsはブラウザとNode.js(サーバサイド)からの呼出しに対応させます。

index.js
import Vue from 'vue'
import App from './App'

const createApp = () => {
  return new Vue({
    render: h => h(App)
  })
}

if (typeof module !== 'undefined' && module.exports) {
  // Node.jsから呼出した場合の処理を追加
  module.exports = createApp
} else {
  // ブラウザから呼出した場合は特に変更無し
  createApp().$mount('#app')
}

index.htmlはパスだけ変更

index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>sample ssr</title>
  </head>
  <body>
    <div id="app" />
    <script src="/dist/client-bundle.js"></script>
  </body>
</html>

サーバサイド作成

Node.js + Expressを使って作成しています。

server.js
const fs = require('fs')
const path = require('path')
const express = require('express')
const renderer = require('vue-server-renderer').createRenderer()
const createApp = require('./dist/server-bundle')

const app = express()
const layout = fs.readFileSync('./dist/index.html', 'utf-8')
const layoutSections = layout.split('<div id="app" />')

app.use('/dist', express.static(
  path.resolve(__dirname, 'dist')
))

app.get('*', (req, res) => {
  // 上記で分割したHTMLをストリーミング
  const stream = renderer.renderToStream(createApp())

  res.write(layoutSections[0])
  stream.on('data', (chunk) => {
    res.write(chunk)
  })

  stream.on('end', () => {
    res.end(layoutSections[1])
  })

  stream.on('error', (err) => {
    console.error(err)
    return response.status(500).send('Server Error')
  })
})

// 5000番ポートで待機
app.listen(5000, function (err) {
  if (err) throw err
  console.log('Server is running at localhost:5000')
})

WebpackをSSR用に作成

webpack.config.js
// ブラウザ用
const client = {
  entry: [
    `${__dirname}/../src/index.html`,
    `${__dirname}/../src/index.js`
  ],
  output: {
    filename: 'client-bundle.js',
    path: `${__dirname}/../dist`
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        use: 'vue-loader',
        exclude: /node_modules/
      },
      {
        test: /\.js$/,
        use: 'babel-loader',
        exclude: /node_modules/
      },
      {
        test: /\.vue$|\.js$/,
        use: 'eslint-loader',
        exclude: /node_modules/
      },
      {
        test: /\.html$/,
        use: 'file-loader?name=[name].[ext]'
      }
    ]
  },
  resolve: {
    extensions: ['.js', '.vue']
  }
}

// サーバ用
const server = Object.assign({}, client, {
  target: 'node',
  output: {
    filename: 'server-bundle.js',
    path: `${__dirname}/../dist`,
    libraryTarget: 'commonjs2'
  },
  externals: Object.keys(require('../package.json').dependencies)
})

module.exports = [client, server]

以下はBabelの設定です。
私の環境だと以下のプラグインが無いと動きませんでした。
「babel-plugin-transform-es2015-modules-commonjs」

.babelrc
{
  "presets": [["es2015", {"modules": false}], "stage-2"],
  "plugins": ["transform-runtime", "transform-es2015-modules-commonjs"]
}

動作確認

以下のコマンドでビルドしてhttp://localhost:5000で見てみると
SSR未対応の時と同様に「sample ssr!!」という文字だけが表示されます。

webpack --config build/webpack.config.js
node server.js

ここでHTMLソースを見てみると
 2017-04-03 18.41.31.png

先程と違い、<div id="app" server-rendered="true"><span>sample ssr!!</span></div>のようにSSRされていることが分かります。
今回は試してませんがvue-routerを使ってあげると、初期表示はSSRして、それ以降はSPAとして動作してくれるはずです。

35
29
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?