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

提示

2023/02/20 更新:將 Inertia 的使用方式更新到 v1 版,以及把 Laravel Mix 換成 Vite。

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

這一篇文章中會記錄在在一個現成的 Inertia 專案中加入 SSR 功能的過程,下面我們一起來看吧~

Inertia 中 SSR 的運作原理

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

更新 Inertia

首先需要一個現成的 Inertia 專案,在現在新版 Laravel 預設都使用 Vite 來打包前端,因此我下面都會用 Vite 的配置。還有要更新 Inertia 到最新版才會支援 SSR 功能。

安裝 Laravel 端到最新版:

composer require inertiajs/inertia-laravel

安裝 Vue 端 (使用 NPM 或 Yarn):

npm install @inertiajs/vue3
yarn add @inertiajs/vue3

如果你的專案裡還有 @inertiajs/inertia@inertiajs/inertia-vue3 的套件的話,表示還是在舊版,可以參考 升級指南 升級,基本上都是改改套件名字、變數名字等,升級難度應該不會太大。

設置 SSR 應用

現在就要開始配置 SSR 了,如果是在 Vue 3 新版可以不需要安裝官方的 SSR 套件 @vue/server-renderer,因為 Vue 核心套件會連帶安裝了,可以改用 vue/server-renderer 來使用。

同樣 Inertia v1 版也會一起安裝 @inertiajs/server 套件,裡面包含了一個執行 SSR 渲染的 HTTP Server。

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

touch resources/js/ssr.js

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

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

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

createServer(page =>
  createInertiaApp({
    page,
    render: renderToString,
    resolve: name => {
      const pages = import.meta.glob('./Pages/**/*.vue', { eager: true })
      return pages[`./Pages/${name}.vue`]
    },
    setup({ App, props, plugin }) {
      return createSSRApp({
        render: () => h(App, props),
      }).use(plugin)
    },
  })
)

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

在 Vue 3 要啟用 hydration 時,要將 app.js 裡的 createApp 替換成 createSSRApp

import { createSSRApp, h } from 'vue'
import { createInertiaApp } from '@inertiajs/vue3'

createInertiaApp({
  resolve: name => {
    const pages = import.meta.glob('./Pages/**/*.vue', { eager: true })
    return pages[`./Pages/${name}.vue`]
  },
  setup({ el, App, props, plugin }) {
    createSSRApp({ render: () => h(App, props) })
      .use(plugin)
      .mount(el)
  },
})

Hydration 相關更詳細的部分可以參考 Vue 的 hydration 注意事項

設置 Vite

然後就進到 Vite 的編譯部分了,這裡需要在 vite.config.js 裡增加剛才的 ssr.js 路徑:

export default defineConfig({
  plugins: [
    Laravel({
      input: ['resources/css/app.css', 'resources/js/app.js'],
      ssr: 'resources/js/ssr.js',
      refresh: true,
    }),
    // ...
  ],
})

現在就可以執行 Vite 編譯 JS。原本的 vite build 是編譯用戶端的,所以再加一個 vite build --ssr 用於編譯 SSR 模式的指令:

  "scripts": {
    "dev": "vite",
    "build": "vite build && vite build --ssr"
  },

之後只要執行就可以同時編譯用戶端跟 SSR 了:

npm run build

在 Laravel 中啟用 SSR

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

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

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

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

然後會看到以下設定,基本上可以先用預設的,如果要改設定可以在這邊改。enabled 是一個單純的開關,url 是 SSR server 的網址,bundle 則是 vite build --ssr 產生的檔案的位置:

<?php

return [

    'ssr' => [

        'enabled' => true,

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

        // 'bundle' => base_path('bootstrap/ssr/ssr.mjs'),

    ],

畢竟編譯出來的檔案不要送進版控,當然要加到 .gitignore 裡:

/bootstrap/ssr
/node_modules
...

執行 SSR 應用

當以上步驟都做完之後,現在就可以來啟動 SSR 網站了,啟動基本的 Artisan Serve:

php artisan serve

然後啟動 SSR 服務:

php artisan inertia:start-ssr

如果在正式環境下,更新完程式碼會需要手動關閉可以執行:

php artisan inertia:stop-ssr

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

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

Inertia 用戶端渲染原始碼

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

提示

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

Inertia 服務端渲染原始碼

結語

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

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

參考資料