Publicité
Publicité

Contenu connexe

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

Publicité

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

  1. 初夏のJavaScript 祭in mixi Vue.js 2.0 で自社プロダクトを SPA + SSR 化した話
  2. Yutaro Miyazaki (@vwxyutarooo) ニート↓ フリーランス(Web制作)↓ アプリ屋のWeb(フロントエンド)
  3. 今日話すこと 導入してみてどうだった?ってとこ 地味に困ったこと
  4. 今日話さないこと Vue.jsSSRのしくみ、ロジックなど 他フレームワークとの比較
  5. サービスの概要 マンガ無料配信サービス アプリを主軸に展開しているサービス Webでもコンテンツを活かそう
  6. リニューアルと導入の背景 Web経験者無しでv1を作ってしまった イケてない
  7. Webでももっとこう アプリっぽい体験できないですかね
  8. v1:Riotでページ毎にマウント →SPA クライアントレンダリング →SSR(SEOほんとにいいのか?) Vue.jsの評判がいい どっかの調査で満足度1位
  9. 構成 (全体)
  10. 構成 (Web)
  11. 前提知識
  12. vuejs/vue‑hackernews‑2.0 公式が作るSPA+SSRプロジェクト https://github.com/vuejs/vue‑hackernews‑2.0
  13. Webpack の例 https://ssr.vuejs.org
  14. 地味に悩んだポイント集
  15. SSRは誰がやる? 404ハンドリング デバイス切り替えってどうする? 共通処理どうする? メタタグの管理めんどいやりたくない Analyticsどうしよう? 広告 メモリリーク
  16. Q: SSR は誰がやる?
  17. バックエンドからもJSが起動できる go go‑duktape goja+goja‑node
  18. 素直にExpressから起動することに
  19. Q: 404 ハンドリング
  20. vue‑hackernews2では ルータ設定にマッチするページが見つからなければ Expressが404返すようになってる
  21. if (err && err.code === 404) { res.status(404).end('404 | Page Not Found') }
  22. 404ページのデザイン欲しい // router.js [ { path: '/', name: 'top', component: top }, ... { path: '*', name: 'not-found', component: notFound } ]
  23. // server.js const termRoute = (context.state.route.name === 'not-found'); if (termRoute) res.status(404);
  24. ルータにマッチするけど404の時は? // router.js { path: '/comics/tag/:id', name: 'tag-archive', component: tagArchive },
  25. APIリクエスト時に、メインクエリを設定 // preFetch const options = { isMainQuery: (key === mainQueryKey) }
  26. メインクエリの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 }); }
  27. Expressサーバで、コンテキストを通じて stateのエラーからステータスを打つ // server.js const termState = (context.state.error); // 404 | 50x if (termState) res.status(context.state.error);
  28. ちょっとイケてないけど 対象Viewコンポーネント内でnotfoundを表示させた <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>
  29. Q: デバイス切り替えってどうする?
  30. 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') }
  31. テンプレートも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'] }),
  32. 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) };
  33. 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)... }
  34. 2.3.0からcreateRenderer にバンドル突っ込むのは非推奨に...
  35. 別の方法を考え中
  36. Serverバンドルはエントリーポイント分けず contextにデバイス情報渡して切り替えるのもありか?
  37. Q: 共通処理どうする?
  38. vuejs/vue-class-component もともとTypeScriptで書けるようにするため Classでコンポーネントを定義できる 継承は非対応だが、Decoratorと組み合わせる
  39. 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() } ... }
  40. 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' } }; });
  41. 使い方は君しだい!
  42. Q: メタタグの管理めんどいやりたくない
  43. declandewet/vue-meta
  44. 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 } ... ] } }
  45. コンポーネントの深いやつが勝つ 全コンポーネント検査してるからパフォーマンスは疑問 SSR対応(Vueの公式にも例あり)
  46. Q: Analytics どうしよう?
  47. WebAnalytics:MatteoGabriele/vue-analytics AppAnalytics:ScreamZ/vue-analytics
  48. routerとくっつけて自動でPV|SV送れる
  49. Q: 広告
  50. GoogleAdsenseはSPA非対応
  51. ページのリフレッシュ無しに広告を打ち直すことは禁止 impが絶望的 GoogleAdsense以外のSSP等広告運用が必須
  52. Q: メモリリーク
  53. 起こしてた
  54. 1日でメモリを食い尽くし ガベージコレクション走りまくり、CPU回りまくり APIキャッシュ周りが原因 最新のVuehackernewsでは直ってる
  55. 所感
  56. SSR の実装はそれほど大変ではないのかも 普通にSPAを作る+ちょっとの手間でいい メタタグとかアプリケーション側で扱いたいからついでに SSRしちゃってたり
  57. まあまあ安定稼働もする CPUはそこそこ回るためページキャッシュを併用 コンポーネントキャッシュは考えて設計すべし 状態変化によるケースやslotが多いと効果的ではないかも?
  58. KPI 的には PV/セッション上がり 滞在時間は平行 回遊しやすくなってるって思いたい なんか下がることはなかった
  59. まとめ あり SEO対策としてだけやるなら要らない メタタグさえサーバ側で作れていれば 堅いこと言わずに作ってみようぜ
  60. Vue.js 楽しいな! おい!!
  61. ありがとうございました!
Publicité