antfu-collective / vite-ssg

Static site generation for Vue 3 on Vite
MIT License
1.32k stars 136 forks source link

Preload routes and meta information and store them in Pinia #337

Open Sibren opened 1 year ago

Sibren commented 1 year ago

Hi,

I'm trying to preload the routes from my graphql endpoint, so I can get the routes and meta information (page title, description, etc). My main.js:

import App from './App.vue';
import { ViteSSG } from 'vite-ssg';
import { router } from './router.js';
import { provideApolloClient } from '@vue/apollo-composable';
import { ApolloClient, InMemoryCache } from '@apollo/client/core';
import { useHead } from '@vueuse/head';

const cache = new InMemoryCache();

const apolloClient = new ApolloClient({
    cache,
    uri: 'https://localhost:44351/graphql/',
});

provideApolloClient(apolloClient);

import { createPinia } from 'pinia';
import { usePagesStore } from './stores/pages';

export const createApp = ViteSSG(
    // the root component
    App,
    // vue-router options
    { routes: router },
    // function to have custom setups
    ({ app, router, routes, isClient, initialState }) => {
        const pinia = createPinia();

        if (isClient) {
            pinia.state.value = initialState.pinia || {};
        } else {
            onSSRAppRendered(() => {
                initialState.pinia = pinia.state.value;
            });
        }
        app.use(pinia);
        router.beforeEach((to, from, next) => {
            const pagesStore = usePagesStore(pinia);
            if (!pagesStore.ready) {
                pagesStore.initialize();
            }
            next();
            let toPath = to.fullPath;
            if (!toPath.endsWith('/')) {
                toPath = toPath + '/';
            }
            var currentPage = pagesStore.getPageById(toPath);
            if (currentPage != null) {
                useHead({
                    title: currentPage.name,
                    meta: [
                        {
                            name: 'description',
                            content: currentPage.metaDescription[0].value.value,
                        },
                    ],
                });
            }
        });

        provideApolloClient,
    }
);

router.js:

import Home from '@/pages/Home.vue';
import About from '@/pages/About.vue';
import Blog from '@/pages/Blog.vue';
import BlogItem from '@/pages/BlogItem.vue';

export const router = [
    {
        path: '/',
        name: 'Home',
        component: Home,
    },
    {
        path: '/about',
        name: 'About',
        component: About,
    },
    {
        path: '/blogs',
        name: 'Blog',
        component: Blog,
    },
    {
        path: '/blogs/:url',
        name: 'BlogItem',
        component: BlogItem,
    },
];

pages.js store:

import { defineStore } from 'pinia';

export const usePagesStore = defineStore('pages', {
    state: () => {
        return { pages: [] };
    },
    actions: {
        async initialize() {
            await fetch('https://localhost:44351/graphql/', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    query: `
                    query {
                                contentAll {
                                    nodes {
                                        url
                                        name
                                        metaDescription: properties(
                                            where: { alias: { eq: "metaDescription" } }
                                        ) {
                                            value {
                                                ... on BasicPropertyValue {
                                                    value
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                    `,
                }),
            })
                .then((res) => res.json())
                .then((result) => {
                    this.pages = result.data.contentAll.nodes;
                });
        },
    },
    getters: {
        allPages: (state) => state.pages,
        getPageById: (state) => {
            return (pageUrl) =>
                state.pages.find((page) => page.url === pageUrl);
        },
    },
});

vite.config.js:

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import path from 'path';

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [vue()],
    ssgOptions: {
        script: 'async',
    },
    server: {
        proxy: {
            '/media': {
                target: 'https://localhost:44351',
                changeOrigin: true,
                secure: false,
                ws: true,
            },
        },
    },
    resolve: {
        alias: {
            '@': path.resolve(__dirname, './src'),
            '~bootstrap': path.resolve(__dirname, 'node_modules/bootstrap'),
        },
    },
});

Now when the page loads, it loads the Pinia-store, but it's too late to load the initial data, but it works when I switch to another page. Once I hit F5 on any page, the pinia-store isn't loaded and it shows the default title from the index.html. I'd love to prerender all the routes in my pinia-store, but I don't know how to do this. I've also tried adjusting my app.vue and using serverPrefetch, but all to no avail.

When I build for production, none of the blogpages are created, since router.js doesn't know them yet.

Can somebody help me out?