Multi page ssr vite plugin based on folder - using pinia, vue-router, vite
- auto routing based on file structure
- backend data preload
- nested route
- flexible by design
- high perfomance
site # root vite folder
- pages/
- demo/ # /demo
- page.vue # vue page component
- data.js # backend data optional
- news+id/ # /news/:id?
- page.vue # vue page component
- data.js # backend data optional
- src/
- main.js # main app, use 3d party library
- App.vue # Root Component
- vite.config.js
- index.html # main page
Demo in git examples directory
npm i @opengis/vite-page
// server.js
import renderPage from '@opengis/vite-mpa/renderPage.js';
fastify.get('*', async (req, reply) => {
const url = req.originalUrl;
const html = await renderPage({
url,
vite,
page: {}, // {{ page }} optional
template: 'news', // pages template name optional
seo: { title: page?.title, meta: { description: 'my description', keywords: 'my keywords' } }, // seo title + meta optional
root: 'site',
ctx: { user: null, settings: {}}
})
reply.headers({ 'Cache-Control': 'public, no-cache' })
.type('text/html')
.send(html);
})
<!-- context -->
<template>
user: {{ $user }}
settings: {{ $settings }}
ssr: {{ $ssr }}
url: {{ $url }}
</template>
Server Fastify Example
import fs from 'node:fs'
import path from 'node:path'
import Fastify from 'fastify';
import { createServer as createViteServer, defineConfig } from 'vite';
import renderPage from '@opengis/vite-mpa/renderPage.js';
const fastify = Fastify();
const isProd = process.env.NODE_ENV === 'production';
const vite = !isProd ? await createViteServer({
root: 'site',
server: { middlewareMode: true },
}) : null;
fastify.addHook('onRequest', async (req, reply) => {
if (isProd) return;
// const { user } = req.session?.passport || {};
const next = () => new Promise((resolve) => {
vite.middlewares(req.raw, reply.raw, () => resolve());
});
await next();
});
fastify.get('/assets/:file', async (req, reply) => {
const stream = fs.createReadStream(`site/dist/client/assets/${req.params.file}`);
return stream;
});
fastify.get('*', async (req, reply) => {
const url = req.originalUrl;
const html = await renderPage({ url, vite, root: 'site' })
reply.headers({ 'Cache-Control': 'public, no-cache' })
.type('text/html')
.send(html);
})
fastify.listen({ host: '0.0.0.0', port: process.env.PORT || 3000 }, (err) => {
if (err) {
fastify.log.error(err);
process.exit(1);
}
});
vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import cms from '@opengis/vite-page/plugin.js';
export default defineConfig({
plugins: [vue(), cms()],
})
index.html
<!DOCTYPE html>
<html ${htmlAttrs}>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!--head-tags-->
<!--preload-links-->
</head>
<body ${bodyAttrs}>
<div id="app"><!--app-html--></div>
<script>window.__pinia = {};</script>
<script type="module" src="/src/entry-client.js"></script>
</body>
</html>
/site/pages/demo
- page.vue
- data.js
// data.js - use backend data & function
export default function (page) {
return { title: 1 }
}
<!-- page.vue -->
<script lang="js">
import { computed, defineComponent, reactive } from "vue";
import { useStore } from "/src/store.js";
export default defineComponent({
name: "Demo",
setup() {
const store = useStore();
const title = computed(() => store.data.title);
const state = reactive({ count: 0 });
return {
state,
title
};
},
});
</script>
<template>
<h1 class="title">DEMO {{ title }}</h1>
<button @click="state.count++">count is: {{ state.count }}</button>
</template>