Search code examples
vue.jsviteserver-side-renderinginertiajssplidejs

Vite, VueJs, Inertia, SSR and dynamic javascript package loading


I am using

I am trying to achieve the following:

I want to use ssr and have a vue component that uses the SplideJS - vue package. This package shouldn't load with ssr, since it dynamically adapts to screensizes etc. I just want it to skip loading in ssr and beeing hydrated only clientside.

My attempts:

"ignoring it in the vite.config.mjs via "external" and "isCustomElement"

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'
            ]
        },
    },

});

My main.js file

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',
    },
});

My 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);
        },
    }),
);

In my component I tried:

<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";

// Splide options
// only true in client
const isClient = ref(false);

// dynamic component
const splideComponent = ref(null);

// after dom is mounted, import splide component
onMounted(() => {
    isClient.value = true;
    import('@splidejs/vue-splide').then(module => {
        splideComponent.value = module.Splide;
    });

});

const options = ref({
   options ...
});

When I start the inertia-ssr server via php artisan inertia:start-ssr no error is received except in my firefox console:

[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> 

I am building via "build": "vite build && vite build --ssr".

Everything works fine without ssr. Right now it is sliding too, but isn't responsive at all + the error is confusing me. I copied a lot of code from a fresh laravel project but unfortunately there's not a lot of docu about "disable packages for ssr"

Any ideas what's the correct way to make this work?


Solution

  • Solved it via:

     <div v-if="isClient" class="splide1">
            <component :is="splideComponent" :options="options"
            >
                <component :is="splideSlideComponent">
                   Slide content..
                </component>
                <component :is="splideSlideComponent">
                   Slide content..
                </component>
        </div>
    </template>
    
    <script setup>
    import {onMounted, ref, shallowRef} from "vue";
    
    const isClient = ref(false);
    
    const splideComponent = shallowRef(null);
    const splideSlideComponent = shallowRef(null);
    
    onMounted(async () => {
        if (typeof window !== 'undefined') {
            const { Splide, SplideSlide } = await import('@splidejs/vue-splide'); // Beide Komponenten importieren
            splideComponent.value = Splide;
            splideSlideComponent.value = SplideSlide;
            isClient.value = true;
        }
    });
    
    const options = ref({
        perPage: 1,
    });
    </script>