Inertia.js SSR | 让 Laravel x Vue 实现服务端渲染

Laravel框架
731
0
0
2022-08-17

本文同步刊载于我的博客 「星星的筆記.Lucas」:lucas-yang.vercel.app/post/inertia...

构建 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 网站变得比较轻松~

参考资料

本文同步刊载于我的博客 「星星的筆記.Lucas」:lucas-yang.vercel.app/post/inertia...