Inertia.js SSR | 讓 Laravel x Vue 實現服務端渲染

2022/01/12 laravelvuejsSSR

構建 Laravel 全端網站我最愛用的 Inertia,現在終於也正式釋出 SSR 的功能,補足了 SPA 網站會有的 SEO 問題了!

這一篇文章中會記錄我在官方範例 PingCRM Vue 2/3 中加入 SSR 功能的過程,下面我們一起來看吧~

# Inertia 中 SSR 的運作原理

首先先大概介紹一下運作原理,Inertia 要適配 PHP 或 Ruby 等不同語言,因此使用了 Node.js 來實作 SSR 服務。在 SSR 模式下接收到請求後會直接將 page 物件 傳入 SSR Server 做解析,最後再回傳 HTML 給瀏覽器,結束這一次的請求。

# 安裝 PingCRM

為了能馬上體驗 SSR 功能,我直接用官方的 PingCRM 範例來實作 SSR 功能,安裝方式使用了 README 的步驟,可以節省配置其他設定的時間。如果你已經熟悉 Laravel 的安裝方式的話,執行這些步驟應該很容易~

下面看要使用哪個 Vue 版本的 PingCRM,直接照 README 的方式來安裝:

不過 Vue 2 的 PingCRM 需要更新一下 resources/js/app.jscreateInertiaApp() 設置部分:





 
 
 
 
 
 


...
createInertiaApp({
  resolve: name => require(`./Pages/${name}`),
  title: title => `${title} - Ping CRM`,
  setup({ el, App, props, plugin }) {
    Vue.use(plugin)
    new Vue({
      render: h => h(App, props),
    }).$mount(el)
  },
})

# 更新 Inertia

裝好 PingCRM 後,需要更新 Inertia 到最新版,才會支援 SSR 功能。

更新 Laravel 端:

composer require inertiajs/inertia-laravel:^0.5.0

更新 Vue 端 (使用 NPM 或 Yarn):

# Vue 2
npm install @inertiajs/inertia@^0.11.0 @inertiajs/inertia-vue@^0.8.0
yarn add @inertiajs/inertia@^0.11.0 @inertiajs/inertia-vue@^0.8.0

# Vue 3
npm install @inertiajs/inertia@^0.11.0 @inertiajs/inertia-vue3@^0.6.0
yarn add @inertiajs/inertia@^0.11.0 @inertiajs/inertia-vue3@^0.6.0

# 設置 SSR 應用

然後就要開始配置 SSR 了,先要安裝 Vue 官方提供的 SSR 套件:

# Vue 2
npm install vue-server-renderer
yarn add vue-server-renderer

# Vue 3
npm install @vue/server-renderer
yarn add @vue/server-renderer

然後要安裝 Inertia 的 @inertiajs/server 套件,這個套件包含了一個 HTTP Server 來執行 SSR 渲染。雖然這個套件並不是非裝不可,不過有裝的話,倒是可以省去自己寫 Server 的時間:

npm install @inertiajs/server
yarn add @inertiajs/server

之後新增一個 resources/js/ssr.js 檔案:

touch resources/js/ssr.js

這個 ssr.js 檔案跟 app.js 有點相似,但是個只會在服務端 (Node.js) 中執行的入口檔案,基本上不影響服務端渲染的套件都可以不用載入,比如 @inertiajs/progress 套件不需要在服務端運行;而服務端/用戶端兩邊都會載入的部分就需要做兩邊兼容,比如需要顧慮到 Node.js 中沒有 windowdocument 的問題。

下面提供 resources/js/ssr.js 的 Vue 2/3 範例:

Vue 2:

import Vue from 'vue'
import { createRenderer } from 'vue-server-renderer'
import { createInertiaApp } from '@inertiajs/inertia-vue'
import createServer from '@inertiajs/server'

createServer((page) => createInertiaApp({
  page,
  render: createRenderer().renderToString,
  resolve: name => require(`./Pages/${name}`),
  setup({ app, props, plugin }) {
    Vue.use(plugin)
    return new Vue({
      render: h => h(app, props),
    })
  },
}))

Vue 3:

import { createSSRApp, h } from 'vue'
import { renderToString } from '@vue/server-renderer'
import { createInertiaApp } from '@inertiajs/inertia-vue3'
import createServer from '@inertiajs/server'

createServer((page) => createInertiaApp({
  page,
  render: renderToString,
  resolve: name => require(`./Pages/${name}`),
  setup({ app, props, plugin }) {
    return createSSRApp({
      render: () => h(app, props),
    }).use(plugin)
  },
}))

# 設置 Laravel Mix

然後就進到 Laravel Mix 的編譯部分了,這裡需要用到 webpack-node-externals 套件:

npm install webpack-node-externals
yarn add webpack-node-externals

開一個 webpack.ssr.mix.js 檔案來編譯 SSR 的部分,因為也是只需要在服務端運行,所以只編譯 JS,不需要管 CSS 了。在這裡比較需要注意的是,webpackConfig() 裡的 target 必須要設為 nodeexternals 需要引入 [webpackNodeExternals()],還有有使用到的 alias 也必須都要設定:

const path = require('path')
const mix = require('laravel-mix')
const webpackNodeExternals = require('webpack-node-externals')

mix
  .options({ manifest: false })
  .js('resources/js/ssr.js', 'public/js')
  .vue({ options: { optimizeSSR: true } })
  .alias({ '@': path.resolve('resources/js') })
  .webpackConfig({
    target: 'node',
    externals: [webpackNodeExternals()],
  })

並執行 Mix 編譯 JS。注意這裡就有兩個部份要執行,一個是原本給用戶端的,另一個是剛剛建立給 SSR 的,一旦應用中有任何修改,都必須要執行這兩個編譯指令:

npx mix
npx mix --mix-config=webpack.ssr.mix.js

如果要編譯 production 的版本,可以把以下設定更新到 package.jsonscripts 中,之後就可以執行 npm run prod 了:

"prod": "mix --production && mix --production --mix-config=webpack.ssr.mix.js",

# 在 Laravel 中啟用 SSR

開啟 app.blade.php,也就是 Inertia 專案的進入點,把 @inertiaHead 加到 <head> 的底部:





 






<!DOCTYPE html>
<html>
  <head>
    ...
    @inertiaHead
  </head>
  <body>
    @inertia
  </body>
</html>

好了之後就可以來啟用 SSR 功能了!先發布 inertia.php 設定檔到專案中:

php artisan vendor:publish --provider="Inertia\ServiceProvider"

然後會看到以下設定,把 ssrenabled 設定改成 true 就可以了:





















 







<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Server Side Rendering
    |--------------------------------------------------------------------------
    |
    | These options configures if and how Inertia uses Server Side Rendering
    | to pre-render the initial visits made to your application's pages.
    |
    | Do note that enabling these options will NOT automatically make SSR work,
    | as a separate rendering service needs to be available. To learn more,
    | please visit https://inertiajs.com/server-side-rendering
    |
    */

    'ssr' => [

        'enabled' => true,

        'url' => 'http://127.0.0.1:13714/render',

    ],

// ...

# 執行 SSR 應用

當以上步驟都做完之後,現在就可以來啟動 SSR 網站了。如果照著 PingCRM 的步驟安裝的話,現在需要啟動基本的 Artisan Serve:

php artisan serve

然後啟動 SSR 服務:

node public/js/ssr.js

現在就可以開瀏覽器看看啦~ 正常的話直接看是沒有差別的,這時候打開網頁原始碼,你就會看到差別所在。

首先是沒有 SSR,單純的用戶端渲染,內容很單純:

Inertia 用戶端渲染原始碼

然後是啟用 SSR 的服務端渲染,就會看到已經渲染好的 <title> 以及 <body> 中的 HTML 了:

可以把瀏覽器左上的 自動換行 打開

Inertia 服務端渲染原始碼

這裡稍微提一下 Vue 的 hydration 操作,在 SSR 模式中因為已經把 HTML 渲染好了,Vue 接手的時候就不需要再重新渲染一遍,而是使用 hydration 的方式,直接把靜態的 HTML 轉換成可以與 Vue 互動的動態 DOM。但有個前提是 服務端 生成的 DOM 和 用戶端 的必須保持一致,如果遇到不同的話會直接中斷 hydration,不管原本的 DOM,直接重新渲染新的。想要了解詳細的部分可以參考 Vue 2 / Vue 3 的 hydration 注意事項。

# 結語

其實在去年 (2021) Inertia 的 SSR 功能早期釋出時我就已經體驗過了,不過當時還沒有把功能包進套件中,使用起來比較麻煩一些,現在正式釋出的版本終於可以比較容易使用了。

在還沒出 SSR 功能之前,我都把 Inertia 定位成只能做後台操作的部分,需要 SEO 的頁面就只能用 blade 來頂替。SSR 功能的出現終於可以做完整的網頁功能了。可以把 Laravel 和 Vue 的功能發揮到極緻,這就是我喜歡 Inertia 的原因~ 期望之後 Inertia 可以完善更多功能,讓用 Laravel + Vue 來建立 Modern SPA 網站變得比較輕鬆~

# 參考資料