vue 3 服务器端渲染与 Vuex 和路由器
vue 3 Server Side Rendering with Vuex and Router
我已经使用 Vue CLI 创建了一个 Vue3 应用程序,以使用 Vuex 和路由器创建我的应用程序。该应用程序 运行 很好。
注意:我按照 Vue3 的 Vuex 这个有用的文档 https://blog.logrocket.com/using-vuex-4-with-vue-3/
要求 现在我想更改我的 Vue3 应用程序以支持服务器端渲染(即 SSR)。
我观看了这个关于使用 Vue3 创建 SSR 应用程序的精彩视频:https://www.youtube.com/watch?v=XJfaAkvLXyU 我可以创建和 运行 一个像视频中那样的简单应用程序。然而,当我试图将它应用到我的主要 Vue3 应用程序时,我被卡住了。
我目前的症结是如何在服务器代码上指定路由器和vuex。
我的代码
客户端入口文件(src/main.js)有以下内容
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
createApp(App).use(store).use(router).mount('#app');
服务器入口文件(src/main.server.js)目前有以下内容
import App from './App.vue';
export default App;
并且在 express 服务器文件 (src/server.js) 中它目前有
const path = require('path');
const express = require('express');
const { createSSRApp } = require('vue');
const { renderToString } = require('@vue/server-renderer');
...
...
server.get('*', async (req, res) => {
const app = createSSRApp(App);
const appContent = await renderToString(app);
我需要更改此代码,以便服务器端的应用程序像在客户端一样使用路由器和 vuex。
问题
在 express 服务器文件中,我无法像在客户端入口文件中那样导入路由器和 vuex,因为在模块外部导入失败,因此在 express 服务器中,我无法执行以下操作
const app = createSSRApp(App).use(store).use(router);
我已经尝试将服务器入口文件 (src/main.server.js) 更改为以下内容,但这也不起作用。
import App from './App.vue';
import router from './router';
import store from './store';
const { createSSRApp } = require('vue');
export default createSSRApp(App).use(store).use(router);
有谁知道当您的应用程序使用 Vuex 和 Router 时如何在 Vue 3 中执行 SSR。
我是如何在 Vue 2 中做到这一点的如下所示,我正在尝试将其转换为 Vue 3
此应用程序的 Vue2 版本具有以下代码
src/app.js 使用指定的路由器和存储创建 Vue 组件
客户端入口文件 (src/client/main.js) 从 app.js 获取应用程序,使用 html 中序列化的数据预填充 Vuex 存储,在路由器上安装应用程序准备就绪
import Vue from 'vue';
import { sync } from 'vuex-router-sync';
import App from './pages/App.vue';
import createStore from './vuex/store';
import createRouter from './pages/router';
export default function createApp() {
const store = createStore();
const router = createRouter();
sync(store, router);
const app = new Vue({
router,
store,
render: (h) => h(App),
});
return { app, router, store };
}
服务器入口文件(src/server/main.js),从app.js获取应用程序,获取匹配的路由,这将在每个组件上调用“serverPrefetch”以获取其数据填充在 Vuex 存储中,然后 returns 解析承诺
import createApp from '../app';
export default (context) => new Promise((resolve, reject) => {
const { app, router, store } = createApp();
router.push(context.url);
router.onReady(() => {
const matchedComponents = router.getMatchedComponents();
if (!matchedComponents.length) {
return reject(new Error('404'));
}
context.rendered = () => {
context.state = store.state;
};
return resolve(app);
}, reject);
});
Express 服务器 (/server.js) 使用捆绑渲染器将应用程序渲染为字符串以放入 html
const fs = require('fs');
const express = require('express');
const { createBundleRenderer } = require('vue-server-renderer');
const dotenv = require('dotenv');
dotenv.config();
const bundleRenderer = createBundleRenderer(
require('./dist/vue-ssr-server-bundle.json'),
{
template: fs.readFileSync('./index.html', 'utf-8'),
},
);
const server = express();
server.use(express.static('public'));
server.get('*', (req, res) => {
const context = {
url: req.url,
clientBundle: `client-bundle.js`,
};
bundleRenderer.renderToString(context, (err, html) => {
if (err) {
if (err.code === 404) {
res.status(404).end('Page not found');
} else {
res.status(500).end('Internal Server Error');
}
} else {
res.end(html);
}
});
});
const port = process.env.PORT || 3000
server.listen(port, () => {
console.log(`Listening on port ${port}`);
});
借助以下资源,我设法找到了解决方案:
服务器端呈现 Vue.js 3 个视频:https://www.youtube.com/watch?v=XJfaAkvLXyU&feature=youtu.be and git repos: https://github.com/moduslabs/vue3-example-ssr
SSR + Vuex + 路由器应用:https://github.com/shenron/vue3-example-ssr
从 Vue 2 迁移到 Vue 3
https://v3.vuejs.org/guide/migration/introduction.html
从 VueRouter 3 迁移到 VueRouter 4
https://next.router.vuejs.org/guide/migration/
从 Vuex 3 迁移到 Vuex 4
https://next.vuex.vuejs.org/guide/migrating-to-4-0-from-3-x.html
客户端入口文件(src/main.js)
import buildApp from './app';
const { app, router, store } = buildApp();
const storeInitialState = window.INITIAL_DATA;
if (storeInitialState) {
store.replaceState(storeInitialState);
}
router.isReady()
.then(() => {
app.mount('#app', true);
});
服务器入口文件(src/main-server.js)
import buildApp from './app';
export default (url) => new Promise((resolve, reject) => {
const { router, app, store } = buildApp();
// set server-side router's location
router.push(url);
router.isReady()
.then(() => {
const matchedComponents = router.currentRoute.value.matched;
// no matched routes, reject with 404
if (!matchedComponents.length) {
return reject(new Error('404'));
}
// the Promise should resolve to the app instance so it can be rendered
return resolve({ app, router, store });
}).catch(() => reject);
});
src/app.js
import { createSSRApp, createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
const isSSR = typeof window === 'undefined';
export default function buildApp() {
const app = (isSSR ? createSSRApp(App) : createApp(App));
app.use(router);
app.use(store);
return { app, router, store };
}
server.js
const serialize = require('serialize-javascript');
const path = require('path');
const express = require('express');
const fs = require('fs');
const { renderToString } = require('@vue/server-renderer');
const manifest = require('./dist/server/ssr-manifest.json');
// Create the express app.
const server = express();
// we do not know the name of app.js as when its built it has a hash name
// the manifest file contains the mapping of "app.js" to the hash file which was created
// therefore get the value from the manifest file thats located in the "dist" directory
// and use it to get the Vue App
const appPath = path.join(__dirname, './dist', 'server', manifest['app.js']);
const createApp = require(appPath).default;
const clientDistPath = './dist/client';
server.use('/img', express.static(path.join(__dirname, clientDistPath, 'img')));
server.use('/js', express.static(path.join(__dirname, clientDistPath, 'js')));
server.use('/css', express.static(path.join(__dirname, clientDistPath, 'css')));
server.use('/favicon.ico', express.static(path.join(__dirname, clientDistPath, 'favicon.ico')));
// handle all routes in our application
server.get('*', async (req, res) => {
const { app, store } = await createApp(req);
let appContent = await renderToString(app);
const renderState = `
<script>
window.INITIAL_DATA = ${serialize(store.state)}
</script>`;
fs.readFile(path.join(__dirname, clientDistPath, 'index.html'), (err, html) => {
if (err) {
throw err;
}
appContent = `<div id="app">${appContent}</div>`;
html = html.toString().replace('<div id="app"></div>', `${renderState}${appContent}`);
res.setHeader('Content-Type', 'text/html');
res.send(html);
});
});
const port = process.env.PORT || 8080;
server.listen(port, () => {
console.log(`You can navigate to http://localhost:${port}`);
});
vue.config.js
用于指定webpack构建的东西
const ManifestPlugin = require('webpack-manifest-plugin');
const nodeExternals = require('webpack-node-externals');
module.exports = {
devServer: {
overlay: {
warnings: false,
errors: false,
},
},
chainWebpack: (webpackConfig) => {
webpackConfig.module.rule('vue').uses.delete('cache-loader');
webpackConfig.module.rule('js').uses.delete('cache-loader');
webpackConfig.module.rule('ts').uses.delete('cache-loader');
webpackConfig.module.rule('tsx').uses.delete('cache-loader');
if (!process.env.SSR) {
// This is required for repl.it to play nicely with the Dev Server
webpackConfig.devServer.disableHostCheck(true);
webpackConfig.entry('app').clear().add('./src/main.js');
return;
}
webpackConfig.entry('app').clear().add('./src/main-server.js');
webpackConfig.target('node');
webpackConfig.output.libraryTarget('commonjs2');
webpackConfig.plugin('manifest').use(new ManifestPlugin({ fileName: 'ssr-manifest.json' }));
webpackConfig.externals(nodeExternals({ allowlist: /\.(css|vue)$/ }));
webpackConfig.optimization.splitChunks(false).minimize(false);
webpackConfig.plugins.delete('hmr');
webpackConfig.plugins.delete('preload');
webpackConfig.plugins.delete('prefetch');
webpackConfig.plugins.delete('progress');
webpackConfig.plugins.delete('friendly-errors');
// console.log(webpackConfig.toConfig())
},
};
src/router/index.js
import { createRouter, createMemoryHistory, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';
const isServer = typeof window === 'undefined';
const history = isServer ? createMemoryHistory() : createWebHistory();
const routes = [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/about',
name: 'About',
component: About,
},
];
const router = createRouter({
history,
routes,
});
export default router;
src/store/index.js
import Vuex from 'vuex';
import fetchAllBeers from '../data/data';
export default Vuex.createStore({
state() {
return {
homePageData: [],
};
},
actions: {
fetchHomePageData({ commit }) {
return fetchAllBeers()
.then((data) => {
commit('setHomePageData', data.beers);
});
},
},
mutations: {
setHomePageData(state, data) {
state.homePageData = data;
},
},
});
Github示例代码
我发现我需要逐步构建代码,只做 SSR、Router、Vuex,然后将它们放在一起。
我的测试应用程序在 github
https://github.com/se22as/vue-3-with-router-basic-sample
- “master”分支:只是一个带有路由器的 vue 3 应用程序
- “added-ssr”分支:采用“master”分支并添加 ssr 代码
- “add-just-vuex”分支:采用“master”分支并添加 vuex 代码
- “added-vuex-to-ssr”分支:带有路由器、vuex 和 ssr 的应用程序。
您还可以使用具有原生 SSR 支持的 Vite,并且与 Webpack 不同,它无需配置即可开箱即用。
如果你使用 vite-plugin-ssr
那就更简单了。
下面重点介绍vite-plugin-ssr
's Vuex example
的主要部分
<template>
<h1>To-do List</h1>
<ul>
<li v-for="item in todoList" :key="item.id">{{item.text}}</li>
</ul>
</template>
<script>
export default {
serverPrefetch() {
return this.$store.dispatch('fetchTodoList');
},
computed: {
todoList () {
return this.$store.state.todoList
}
},
}
</script>
import Vuex from 'vuex'
export { createStore }
function createStore() {
const store = Vuex.createStore({
state() {
return {
todoList: []
}
},
actions: {
fetchTodoList({ commit }) {
const todoList = [
{
id: 0,
text: 'Buy milk'
},
{
id: 1,
text: 'Buy chocolate'
}
]
return commit('setTodoList', todoList)
}
},
mutations: {
setTodoList(state, todoList) {
state.todoList = todoList
}
}
})
return store
}
import { createSSRApp, h } from 'vue'
import { createStore } from './store'
export { createApp }
function createApp({ Page }) {
const app = createSSRApp({
render: () => h(Page)
})
const store = createStore()
app.use(store)
return { app, store }
}
import { renderToString } from '@vue/server-renderer'
import { html } from 'vite-plugin-ssr'
import { createApp } from './app'
export { render }
export { addContextProps }
export { setPageProps }
async function render({ contextProps }) {
const { appHtml } = contextProps
return html`<!DOCTYPE html>
<html>
<body>
<div id="app">${html.dangerouslySetHtml(appHtml)}</div>
</body>
</html>`
}
async function addContextProps({ Page }) {
const { app, store } = createApp({ Page })
const appHtml = await renderToString(app)
const INITIAL_STATE = store.state
return {
INITIAL_STATE,
appHtml
}
}
function setPageProps({ contextProps }) {
const { INITIAL_STATE } = contextProps
return { INITIAL_STATE }
}
import { getPage } from 'vite-plugin-ssr/client'
import { createApp } from './app'
hydrate()
async function hydrate() {
const { Page, pageProps } = await getPage()
const { app, store } = createApp({ Page })
store.replaceState(pageProps.INITIAL_STATE)
app.mount('#app')
}
我已经使用 Vue CLI 创建了一个 Vue3 应用程序,以使用 Vuex 和路由器创建我的应用程序。该应用程序 运行 很好。
注意:我按照 Vue3 的 Vuex 这个有用的文档 https://blog.logrocket.com/using-vuex-4-with-vue-3/
要求 现在我想更改我的 Vue3 应用程序以支持服务器端渲染(即 SSR)。
我观看了这个关于使用 Vue3 创建 SSR 应用程序的精彩视频:https://www.youtube.com/watch?v=XJfaAkvLXyU 我可以创建和 运行 一个像视频中那样的简单应用程序。然而,当我试图将它应用到我的主要 Vue3 应用程序时,我被卡住了。
我目前的症结是如何在服务器代码上指定路由器和vuex。
我的代码
客户端入口文件(src/main.js)有以下内容
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
createApp(App).use(store).use(router).mount('#app');
服务器入口文件(src/main.server.js)目前有以下内容
import App from './App.vue';
export default App;
并且在 express 服务器文件 (src/server.js) 中它目前有
const path = require('path');
const express = require('express');
const { createSSRApp } = require('vue');
const { renderToString } = require('@vue/server-renderer');
...
...
server.get('*', async (req, res) => {
const app = createSSRApp(App);
const appContent = await renderToString(app);
我需要更改此代码,以便服务器端的应用程序像在客户端一样使用路由器和 vuex。
问题
在 express 服务器文件中,我无法像在客户端入口文件中那样导入路由器和 vuex,因为在模块外部导入失败,因此在 express 服务器中,我无法执行以下操作
const app = createSSRApp(App).use(store).use(router);
我已经尝试将服务器入口文件 (src/main.server.js) 更改为以下内容,但这也不起作用。
import App from './App.vue';
import router from './router';
import store from './store';
const { createSSRApp } = require('vue');
export default createSSRApp(App).use(store).use(router);
有谁知道当您的应用程序使用 Vuex 和 Router 时如何在 Vue 3 中执行 SSR。
我是如何在 Vue 2 中做到这一点的如下所示,我正在尝试将其转换为 Vue 3
此应用程序的 Vue2 版本具有以下代码
src/app.js 使用指定的路由器和存储创建 Vue 组件
客户端入口文件 (src/client/main.js) 从 app.js 获取应用程序,使用 html 中序列化的数据预填充 Vuex 存储,在路由器上安装应用程序准备就绪
import Vue from 'vue';
import { sync } from 'vuex-router-sync';
import App from './pages/App.vue';
import createStore from './vuex/store';
import createRouter from './pages/router';
export default function createApp() {
const store = createStore();
const router = createRouter();
sync(store, router);
const app = new Vue({
router,
store,
render: (h) => h(App),
});
return { app, router, store };
}
服务器入口文件(src/server/main.js),从app.js获取应用程序,获取匹配的路由,这将在每个组件上调用“serverPrefetch”以获取其数据填充在 Vuex 存储中,然后 returns 解析承诺
import createApp from '../app';
export default (context) => new Promise((resolve, reject) => {
const { app, router, store } = createApp();
router.push(context.url);
router.onReady(() => {
const matchedComponents = router.getMatchedComponents();
if (!matchedComponents.length) {
return reject(new Error('404'));
}
context.rendered = () => {
context.state = store.state;
};
return resolve(app);
}, reject);
});
Express 服务器 (/server.js) 使用捆绑渲染器将应用程序渲染为字符串以放入 html
const fs = require('fs');
const express = require('express');
const { createBundleRenderer } = require('vue-server-renderer');
const dotenv = require('dotenv');
dotenv.config();
const bundleRenderer = createBundleRenderer(
require('./dist/vue-ssr-server-bundle.json'),
{
template: fs.readFileSync('./index.html', 'utf-8'),
},
);
const server = express();
server.use(express.static('public'));
server.get('*', (req, res) => {
const context = {
url: req.url,
clientBundle: `client-bundle.js`,
};
bundleRenderer.renderToString(context, (err, html) => {
if (err) {
if (err.code === 404) {
res.status(404).end('Page not found');
} else {
res.status(500).end('Internal Server Error');
}
} else {
res.end(html);
}
});
});
const port = process.env.PORT || 3000
server.listen(port, () => {
console.log(`Listening on port ${port}`);
});
借助以下资源,我设法找到了解决方案:
服务器端呈现 Vue.js 3 个视频:https://www.youtube.com/watch?v=XJfaAkvLXyU&feature=youtu.be and git repos: https://github.com/moduslabs/vue3-example-ssr
SSR + Vuex + 路由器应用:https://github.com/shenron/vue3-example-ssr
从 Vue 2 迁移到 Vue 3 https://v3.vuejs.org/guide/migration/introduction.html
从 VueRouter 3 迁移到 VueRouter 4 https://next.router.vuejs.org/guide/migration/
从 Vuex 3 迁移到 Vuex 4 https://next.vuex.vuejs.org/guide/migrating-to-4-0-from-3-x.html
客户端入口文件(src/main.js)
import buildApp from './app';
const { app, router, store } = buildApp();
const storeInitialState = window.INITIAL_DATA;
if (storeInitialState) {
store.replaceState(storeInitialState);
}
router.isReady()
.then(() => {
app.mount('#app', true);
});
服务器入口文件(src/main-server.js)
import buildApp from './app';
export default (url) => new Promise((resolve, reject) => {
const { router, app, store } = buildApp();
// set server-side router's location
router.push(url);
router.isReady()
.then(() => {
const matchedComponents = router.currentRoute.value.matched;
// no matched routes, reject with 404
if (!matchedComponents.length) {
return reject(new Error('404'));
}
// the Promise should resolve to the app instance so it can be rendered
return resolve({ app, router, store });
}).catch(() => reject);
});
src/app.js
import { createSSRApp, createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
const isSSR = typeof window === 'undefined';
export default function buildApp() {
const app = (isSSR ? createSSRApp(App) : createApp(App));
app.use(router);
app.use(store);
return { app, router, store };
}
server.js
const serialize = require('serialize-javascript');
const path = require('path');
const express = require('express');
const fs = require('fs');
const { renderToString } = require('@vue/server-renderer');
const manifest = require('./dist/server/ssr-manifest.json');
// Create the express app.
const server = express();
// we do not know the name of app.js as when its built it has a hash name
// the manifest file contains the mapping of "app.js" to the hash file which was created
// therefore get the value from the manifest file thats located in the "dist" directory
// and use it to get the Vue App
const appPath = path.join(__dirname, './dist', 'server', manifest['app.js']);
const createApp = require(appPath).default;
const clientDistPath = './dist/client';
server.use('/img', express.static(path.join(__dirname, clientDistPath, 'img')));
server.use('/js', express.static(path.join(__dirname, clientDistPath, 'js')));
server.use('/css', express.static(path.join(__dirname, clientDistPath, 'css')));
server.use('/favicon.ico', express.static(path.join(__dirname, clientDistPath, 'favicon.ico')));
// handle all routes in our application
server.get('*', async (req, res) => {
const { app, store } = await createApp(req);
let appContent = await renderToString(app);
const renderState = `
<script>
window.INITIAL_DATA = ${serialize(store.state)}
</script>`;
fs.readFile(path.join(__dirname, clientDistPath, 'index.html'), (err, html) => {
if (err) {
throw err;
}
appContent = `<div id="app">${appContent}</div>`;
html = html.toString().replace('<div id="app"></div>', `${renderState}${appContent}`);
res.setHeader('Content-Type', 'text/html');
res.send(html);
});
});
const port = process.env.PORT || 8080;
server.listen(port, () => {
console.log(`You can navigate to http://localhost:${port}`);
});
vue.config.js
用于指定webpack构建的东西
const ManifestPlugin = require('webpack-manifest-plugin');
const nodeExternals = require('webpack-node-externals');
module.exports = {
devServer: {
overlay: {
warnings: false,
errors: false,
},
},
chainWebpack: (webpackConfig) => {
webpackConfig.module.rule('vue').uses.delete('cache-loader');
webpackConfig.module.rule('js').uses.delete('cache-loader');
webpackConfig.module.rule('ts').uses.delete('cache-loader');
webpackConfig.module.rule('tsx').uses.delete('cache-loader');
if (!process.env.SSR) {
// This is required for repl.it to play nicely with the Dev Server
webpackConfig.devServer.disableHostCheck(true);
webpackConfig.entry('app').clear().add('./src/main.js');
return;
}
webpackConfig.entry('app').clear().add('./src/main-server.js');
webpackConfig.target('node');
webpackConfig.output.libraryTarget('commonjs2');
webpackConfig.plugin('manifest').use(new ManifestPlugin({ fileName: 'ssr-manifest.json' }));
webpackConfig.externals(nodeExternals({ allowlist: /\.(css|vue)$/ }));
webpackConfig.optimization.splitChunks(false).minimize(false);
webpackConfig.plugins.delete('hmr');
webpackConfig.plugins.delete('preload');
webpackConfig.plugins.delete('prefetch');
webpackConfig.plugins.delete('progress');
webpackConfig.plugins.delete('friendly-errors');
// console.log(webpackConfig.toConfig())
},
};
src/router/index.js
import { createRouter, createMemoryHistory, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';
const isServer = typeof window === 'undefined';
const history = isServer ? createMemoryHistory() : createWebHistory();
const routes = [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/about',
name: 'About',
component: About,
},
];
const router = createRouter({
history,
routes,
});
export default router;
src/store/index.js
import Vuex from 'vuex';
import fetchAllBeers from '../data/data';
export default Vuex.createStore({
state() {
return {
homePageData: [],
};
},
actions: {
fetchHomePageData({ commit }) {
return fetchAllBeers()
.then((data) => {
commit('setHomePageData', data.beers);
});
},
},
mutations: {
setHomePageData(state, data) {
state.homePageData = data;
},
},
});
Github示例代码
我发现我需要逐步构建代码,只做 SSR、Router、Vuex,然后将它们放在一起。
我的测试应用程序在 github
https://github.com/se22as/vue-3-with-router-basic-sample
- “master”分支:只是一个带有路由器的 vue 3 应用程序
- “added-ssr”分支:采用“master”分支并添加 ssr 代码
- “add-just-vuex”分支:采用“master”分支并添加 vuex 代码
- “added-vuex-to-ssr”分支:带有路由器、vuex 和 ssr 的应用程序。
您还可以使用具有原生 SSR 支持的 Vite,并且与 Webpack 不同,它无需配置即可开箱即用。
如果你使用 vite-plugin-ssr
那就更简单了。
下面重点介绍vite-plugin-ssr
's Vuex example
<template>
<h1>To-do List</h1>
<ul>
<li v-for="item in todoList" :key="item.id">{{item.text}}</li>
</ul>
</template>
<script>
export default {
serverPrefetch() {
return this.$store.dispatch('fetchTodoList');
},
computed: {
todoList () {
return this.$store.state.todoList
}
},
}
</script>
import Vuex from 'vuex'
export { createStore }
function createStore() {
const store = Vuex.createStore({
state() {
return {
todoList: []
}
},
actions: {
fetchTodoList({ commit }) {
const todoList = [
{
id: 0,
text: 'Buy milk'
},
{
id: 1,
text: 'Buy chocolate'
}
]
return commit('setTodoList', todoList)
}
},
mutations: {
setTodoList(state, todoList) {
state.todoList = todoList
}
}
})
return store
}
import { createSSRApp, h } from 'vue'
import { createStore } from './store'
export { createApp }
function createApp({ Page }) {
const app = createSSRApp({
render: () => h(Page)
})
const store = createStore()
app.use(store)
return { app, store }
}
import { renderToString } from '@vue/server-renderer'
import { html } from 'vite-plugin-ssr'
import { createApp } from './app'
export { render }
export { addContextProps }
export { setPageProps }
async function render({ contextProps }) {
const { appHtml } = contextProps
return html`<!DOCTYPE html>
<html>
<body>
<div id="app">${html.dangerouslySetHtml(appHtml)}</div>
</body>
</html>`
}
async function addContextProps({ Page }) {
const { app, store } = createApp({ Page })
const appHtml = await renderToString(app)
const INITIAL_STATE = store.state
return {
INITIAL_STATE,
appHtml
}
}
function setPageProps({ contextProps }) {
const { INITIAL_STATE } = contextProps
return { INITIAL_STATE }
}
import { getPage } from 'vite-plugin-ssr/client'
import { createApp } from './app'
hydrate()
async function hydrate() {
const { Page, pageProps } = await getPage()
const { app, store } = createApp({ Page })
store.replaceState(pageProps.INITIAL_STATE)
app.mount('#app')
}