Laravel+Inertia SSR环境下如何仅客户端加载SplideJS组件?
问题描述
技术栈
- Laravel 10
- @inertiajs/vue3@2.0.2
- inertiajs/inertia-laravel v2.0.0(Inertia.js的Laravel适配器)
- Vite v5.4.11
- @vitejs/plugin-vue@5.1.3
- @splidejs/vue-splide@0.6.12
需求
启用SSR时,让依赖SplideJS-vue的Vue组件跳过SSR阶段,仅在客户端进行水合(组件需动态适配屏幕尺寸,服务端无法处理)。
尝试的配置与代码
vite.config.mjs
import {defineConfig} from 'vite'; import laravel from 'laravel-vite-plugin'; import vue from '@vitejs/plugin-vue'; import {fileURLToPath, URL} from 'url'; import vuetify from "vite-plugin-vuetify"; export default defineConfig({ plugins: [ laravel({ input: ['resources/js/main.js'], ssr: 'resources/js/ssr.js', refresh: true, }), vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.includes('Splide'), } }, }), vuetify({ autoImport: true, }), ], ssr: { noExternal: ['@inertiajs/vue3/server'], external: ['@splidejs/vue-splide'], }, resolve: { alias: { '@': fileURLToPath(new URL('./resources/js', import.meta.url)), }, resolve: { dedupe: [ 'vue' ] }, }, });
main.js
import { createApp, h } from "vue"; import { createInertiaApp, Link, Head } from "@inertiajs/vue3"; import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; import { ZiggyVue } from '../../vendor/tightenco/ziggy'; import { createPinia } from 'pinia'; import piniaPluginPersistedState from "pinia-plugin-persistedstate"; import '../css/app.css'; import VueSplide from '@splidejs/vue-splide'; import '@splidejs/splide/css'; const appName = "SSR Test"; const pinia = createPinia(); pinia.use(piniaPluginPersistedState); createInertiaApp({ title: (title) => `${title} | ${appName}`, resolve: (name) => resolvePageComponent( `./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue') ), setup({ el, App, props, plugin }) { createApp({ render: () => h(App, props) }) .use(plugin) .use(ZiggyVue) .use(pinia) .use(VueSplide) .component("Link", Link) .component("Head", Head) .mount(el); }, progress: { color: '#000000', }, });
ssr.js
import {createInertiaApp, Head, Link} from '@inertiajs/vue3'; import createServer from '@inertiajs/vue3/server' import { renderToString } from '@vue/server-renderer'; import { createSSRApp, h } from 'vue'; import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; import { createPinia } from 'pinia'; import { ZiggyVue } from 'ziggy-js'; const pinia = createPinia(); createServer((page) => createInertiaApp({ page, render: renderToString, title: (title) => `testtitle`, resolve: (name) => resolvePageComponent( `./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue'), ), setup({ App, props, plugin }) { return createSSRApp({ render: () => h(App, props), }) .use(plugin) .use(pinia) .use(ZiggyVue, { ...page.props.ziggy, location: new URL(page.props.ziggy.location), }) .component("Link", Link) .component("Head", Head); }, }), );
组件代码
<template> <div v-if="isClient"> <component :is="splideComponent" :options="options" aria-label="Unser Kundenservice" > <SplideSlide> Slide 1 </SplideSlide> <SplideSlide> Slide 2 </SplideSlide> <SplideSlide> Slide 3 </SplideSlide> </component> </div> </template> <script setup> import ContentBaseWhite from '@/Shared/Components/c-content-base-white.vue'; import {onMounted, ref} from "vue"; const isClient = ref(false); const splideComponent = ref(null); onMounted(() => { isClient.value = true; import('@splidejs/vue-splide').then(module => { splideComponent.value = module.Splide; }); }); const options = ref({ // 自定义选项配置 }); </script>
遇到的问题
启动SSR服务器php artisan inertia:start-ssr后无服务端错误,但Firefox控制台出现警告:
[Vue warn]: Vue received a Component that was made a reactive object. This can lead to unnecessary performance overhead and should be avoided by marking the component with `markRaw` or using `shallowRef` instead of `ref`. Component that was made reactive: Object { name: "Splide", emits: (27) […], components: {…}, props: {…}, setup: setup(props, context), render: _sfc_render$1(_ctx, _cache, $props, $setup, $data, $options) } at <CCustomerService> at <BasicStoreLayout>
组件虽能滑动但完全不响应,禁用SSR时一切正常。
解决方案
1. 修复响应式组件警告
警告的核心是组件被意外转为响应式对象,有两种修复方式:
方案一:使用shallowRef存储组件
shallowRef不会深度响应化对象,适合存储组件:
<template> <div v-if="isClient"> <component :is="splideComponent" :options="options" aria-label="Unser Kundenservice"> <component :is="splideSlideComponent">Slide 1</component> <component :is="splideSlideComponent">Slide 2</component> <component :is="splideSlideComponent">Slide 3</component> </component> </div> </template> <script setup> import ContentBaseWhite from '@/Shared/Components/c-content-base-white.vue'; import {onMounted, shallowRef, ref} from "vue"; const isClient = ref(false); const splideComponent = shallowRef(null); const splideSlideComponent = shallowRef(null); // 同时处理SplideSlide组件 onMounted(() => { isClient.value = true; import('@splidejs/vue-splide').then(module => { splideComponent.value = module.Splide; splideSlideComponent.value = module.SplideSlide; }); }); const options = ref({ // 自定义选项配置 }); </script>
方案二:用markRaw标记组件为非响应式
如果坚持使用ref,可以在赋值时用markRaw阻止Vue将组件转为响应式:
<script setup> import ContentBaseWhite from '@/Shared/Components/c-content-base-white.vue'; import {onMounted, ref, markRaw} from "vue"; const isClient = ref(false); const splideComponent = ref(null); const splideSlideComponent = ref(null); onMounted(() => { isClient.value = true; import('@splidejs/vue-splide').then(module => { splideComponent.value = markRaw(module.Splide); splideSlideComponent.value = markRaw(module.SplideSlide); }); }); const options = ref({ // 自定义选项配置 }); </script>
2. 优化SSR配置
移除vite.config.mjs中错误的isCustomElement配置(Splide是Vue组件,不是自定义元素),并完善SSR的external规则:
// vite.config.mjs ssr: { noExternal: ['@inertiajs/vue3/server'], external: ['@splidejs/vue-splide', '@splidejs/splide'], // 同时排除Splide核心库 },
3. 移除全局注册的Splide
既然组件是动态导入,不需要在main.js中全局注册,删除以下代码:
import VueSplide from '@splidejs/vue-splide'; // ... .use(VueSplide)
如果全局需要Splide样式,可以保留import '@splidejs/splide/css';。
4. 简化客户端判断逻辑
直接通过typeof window !== 'undefined'判断是否在客户端,无需等待onMounted:
<script setup> import ContentBaseWhite from '@/Shared/Components/c-content-base-white.vue'; import {shallowRef, ref} from "vue"; const isClient = typeof window !== 'undefined'; const splideComponent = shallowRef(null); const splideSlideComponent = shallowRef(null); if (isClient) { import('@splidejs/vue-splide').then(module => { splideComponent.value = module.Splide; splideSlideComponent.value = module.SplideSlide; }); } const options = ref({ // 自定义选项配置 }); </script>
5. 重新构建并启动SSR
执行构建命令:
npm run build
启动SSR服务器:
php artisan inertia:start-ssr
调整后,Splide组件会完全在客户端加载,不参与SSR渲染,同时解决响应式警告,组件交互也会恢复正常。
内容的提问来源于stack exchange,提问作者dogma
相关产品推荐
相关产品推荐

