vue.js 2.0 で自社プロダクトを spa + ssr 化した話

Post on 21-Jan-2018

4.219 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

初夏の JavaScript 祭 in mixi

Vue.js 2.0 で自社プロダクトを SPA + SSR 化した話

Yutaro Miyazaki (@vwxyutarooo)

ニート ↓

フリーランス (Web 制作) ↓

アプリ屋の Web (フロントエンド)

今日話すこと

導入してみてどうだった? ってとこ

地味に困ったこと

今日話さないこと

Vue.js SSR のしくみ、ロジックなど

他フレームワークとの比較

サービスの概要

マンガ無料配信サービス

アプリを主軸に展開しているサービス

Web でもコンテンツを活かそう

リニューアルと導入の背景

Web 経験者無しで v1 を作ってしまった

イケてない

Web でももっとこう

アプリっぽい体験できないですかね

v1: Riot でページ毎にマウント

→ SPA

クライアントレンダリング

→ SSR (SEO ほんとにいいのか?)

Vue.js の評判がいい

どっかの調査で満足度1位

構成 (全体)

構成 (Web)

前提知識

vuejs/vue‑hackernews‑2.0

公式が作る SPA + SSR プロジェクト

https://github.com/vuejs/vue‑hackernews‑2.0

Webpack の例

https://ssr.vuejs.org

地味に悩んだポイント集

SSR は誰がやる?

404 ハンドリング

デバイス切り替えってどうする?

共通処理どうする?

メタタグの管理めんどいやりたくない

Analytics どうしよう?

広告

メモリリーク

Q: SSR は誰がやる?

バックエンドからも JS が起動できる

go

go‑duktape

goja + goja‑node

素直に Express から起動することに

Q: 404 ハンドリング

vue‑hackernews 2 では

ルータ設定にマッチするページが見つからなければ

Express が 404 返すようになってる

if (err && err.code === 404) { res.status(404).end('404 | Page Not Found') }

404 ページのデザイン欲しい

// router.js [ { path: '/', name: 'top', component: top }, ... { path: '*', name: 'not-found', component: notFound } ]

// server.js const termRoute = (context.state.route.name === 'not-found');

if (termRoute) res.status(404);

ルータにマッチするけど 404 の時は?

// router.js { path: '/comics/tag/:id', name: 'tag-archive', component: tagArchive },

API リクエスト時に、メインクエリを設定

// preFetch const options = { isMainQuery: (key === mainQueryKey) }

メインクエリの API レスポンスが

200 じゃなかったら state にエラーをセット

// action.js if (result.status === 200) { commit(mutation, { key, result: result.data }); } else if (options.isMainQuery) { commit(types.SET_STATUS, { key: type, value: {} }); commit(types.SET_STATUS, { key: 'error', value: result.status // 404 }); }

Express サーバで、コンテキストを通じて

state のエラーからステータスを打つ

// server.js const termState = (context.state.error); // 404 | 50x

if (termState) res.status(context.state.error);

ちょっとイケてないけど

対象 View コンポーネント内で not found を表示させた

<div :key="`tag-archives-${id}-${currentPage}`"> <div v-if="isLoading" class="l-root"> <screen-spinner></screen-spinner> </div> <content-not-found v-else-if="status === 404"></content-not-found> <template v-else="v-else"> ... </template> </div>

Q: デバイス切り替えってどうする?

PC/SP 用エントリーポイントをそれぞれ用意

// webpack.config.client.js entry: { 'polyfills': [path.join(..., 'app/entry/polyfills.js')], 'vendor': [path.join(..., 'app/entry/vendor.js')], 'app.pc': [path.join(..., 'app/entry/pc/client-entry.js')], 'app.sp': [path.join(..., 'app/entry/sp/client-entry.js')] },

// webpack.config.server.js entry: { 'server-bundle.pc': path.join(..., 'app/entry/pc/server-entry.js'), 'server-bundle.sp': path.join(..., 'app/entry/sp/server-entry.js') }

テンプレートも2つ

// webpack.config.client.js new HTMLPlugin({ template: path.join(..., 'templates/pc.html'), filename: 'index.pc.html', excludeChunks: ['app.sp'] }), new HTMLPlugin({ template: path.join(..., 'templates/sp.html'), filename: 'index.sp.html', excludeChunks: ['app.pc'] }),

createRenderer でレンダラを2つ作成

// server.js const bundle = { pc: fs.readFileSync(resolve('./dist/js/server-bundle.pc.js'), 'utf-8'), sp: fs.readFileSync(resolve('./dist/js/server-bundle.sp.js'), 'utf-8') } const template = { pc: fs.readFileSync(resolve('./dist/index.pc.html'), 'utf-8'), sp: fs.readFileSync(resolve('./dist/index.sp.html'), 'utf-8') } renderer = { pc: createRenderer(bundle.pc, template.pc), sp: createRenderer(bundle.sp, template.sp) };

Express で UA 判定して起動するレンダラを切り替え

// server.js const useragent = require('express-useragent'); ... app.use(useragent.express()); app.get('*', (req, res) => { ... const device = (req.useragent.isMobile) ? 'sp' : 'pc'; ... renderer[device].renderToStream(context)... }

2.3.0 から createRenderer

にバンドル突っ込むのは非推奨に...

別の方法を考え中

Server バンドルはエントリーポイント分けず

context にデバイス情報渡して切り替えるのもありか?

Q: 共通処理どうする?

vuejs/vue-class-component

もともと TypeScript で書けるようにするため

Class でコンポーネントを定義できる

継承は非対応だが、Decorator と組み合わせる

import Vue from 'vue' import Component from 'vue-class-component'

@Component({ props: { propMessage: String } }) export default class App extends Vue { // initial data msg = 123

// use prop values for initial data helloMsg = 'Hello, ' + this.propMessage

// lifecycle hook mounted () { this.greet() } ... }

import { createDecorator } from 'vue-class-component';

export const Options = createDecorator((options) => { Object.assign(options, { ... watch: { // call again the method if the route changes '$route': 'routeUpdated' } }; });

使い方は君しだい!

Q: メタタグの管理めんどいやりたくない

declandewet/vue-meta

export default { name: 'App', metaInfo: { title: METAINFO.title, titleTemplate: METAINFO.titleTemplate, meta: [ { vmid: 'og:title', name: 'og:title', content: METAINFO.title }, { vmid: 'description', name: 'description', content: METAINFO.description } ... ] } }

コンポーネントの深いやつが勝つ

全コンポーネント検査してるからパフォーマンスは疑問

SSR 対応 (Vue の公式にも例あり)

Q: Analytics どうしよう?

Web Analytics: MatteoGabriele/vue-analytics

App Analytics: ScreamZ/vue-analytics

router とくっつけて自動で PV | SV 送れる

Q: 広告

Google Adsense は SPA 非対応

ページのリフレッシュ無しに広告を打ち直すことは禁止

imp が絶望的

Google Adsense 以外の SSP 等広告運用が必須

Q: メモリリーク

起こしてた

1日でメモリを食い尽くし

ガベージコレクション走りまくり、CPU 回りまくり

API キャッシュ周りが原因

最新の Vuehackernews では直ってる

所感

SSR の実装はそれほど大変ではないのかも

普通に SPA を作る + ちょっとの手間でいい

メタタグとかアプリケーション側で扱いたいからついでに

SSR しちゃってたり

まあまあ安定稼働もする

CPU はそこそこ回るためページキャッシュを併用

コンポーネントキャッシュは考えて設計すべし

状態変化によるケースや slot が多いと効果的ではないかも?

KPI 的には

PV/セッション上がり

滞在時間は平行

回遊しやすくなってるって思いたい

なんか下がることはなかった

まとめ

あり

SEO 対策としてだけやるなら要らない

メタタグさえサーバ側で作れていれば

堅いこと言わずに作ってみようぜ

Vue.js 楽しいな! おい!!

ありがとうございました!

top related