当前位置: 首页 > 知识库问答 >
问题:

Vue 3服务器端渲染与Vuex和路由器

凌蕴藉
2023-03-14

我已经使用Vue CLI创建了一个Vue3应用程序,用Vuex和路由器创建我的应用程序。应用程序运行良好。

注意:我遵循了这个有用的文档,用于Vue3的Vuexhttps://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;

并在当前拥有的快速服务器文件(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 server文件中,我无法在客户端条目文件中导入路由器和vuex,因为它由于在模块外部导入而失败,因此在express server中,我无法执行以下操作

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和路由器时,是否有人知道如何在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),从应用程序获取应用程序。js,获取匹配的路由,该路由将调用每个组件上的“serverPrefetch”以在Vuex存储中填充其数据,然后返回解析promise

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

expressserver(/server.js)使用bundle呈现程序将应用程序呈现为字符串,以放入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}`);
});

共有2个答案

严令秋
2023-03-14

您还可以使用Vite,它具有本地SSR支持,与Webpack不同,无需配置即可使用。

如果你使用vite-plugin-ssr,那就更容易了。

下面重点介绍了vite-plugin-ssr的Vuex示例的主要部分

<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')
}
郭兴平
2023-03-14

多亏了以下资源,我成功地找到了解决方案:

>

SSR Vuex路由器应用程序:https://github.com/shenron/vue3-example-ssr

从Vue 2迁移到Vue 3https://v3.vuejs.org/guide/migration/introduction.html

从VueRout3迁移到VueRout4https://next.router.vuejs.org/guide/migration/

从Vuex 3迁移到Vuex 4https://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 };
}

服务器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。配置。js

用于指定网页包生成内容

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/路由器/索引。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,只是路由器,只是Vuex,然后把它们都放在一起。

我的测试应用程序在github中

https://github.com/se22as/vue-3-with-router-basic-sample

  • “主”分支:只是一个带有路由器的vue 3应用程序
  • “添加ssr”分支:取“主”分支并添加ssr代码
  • “仅添加vuex”分支:获取“主”分支并添加vuex代码
  • “将vuex添加到ssr”分支:带有路由器、vuex和ssr的应用程序
 类似资料:
  • 问题内容: 我刚刚开始研究ReactJS,发现它为您提供了两种渲染页面的方法:服务器端和客户端。但是,我不知道如何一起使用。是使用两种单独的方法来构建应用程序,还是可以将它们一起使用? 如果可以一起使用,该如何做- 我们是否需要在服务器端和客户端重复相同的元素?或者,我们是否可以仅在服务器上构建应用程序的静态部分,而在客户端构建动态部分,而无需与已经预先渲染的服务器端建立任何连接? 问题答案: 对

  • info 如果您能了解下面这些技术,能加快您对本文的了解 vuex - Vue.js 应用程序开发的状态管理模式 Vue.js SSR - Vue.js 服务器端渲染 webpack - 编译构建工具 Lavas 服务器端渲染模板参考了 vue-hackernews 的渲染和开发机制,并且结合了 Lavas 的 App Shell 模板,导出的工程中会有 App Shell 等 如果您不了解 vu

  • React 提供了两个方法 renderToString 和 renderToStaticMarkup 用来将组件(Virtual DOM)输出成 HTML 字符串,这是 React 服务器端渲染的基础,它移除了服务器端对于浏览器环境的依赖,所以让服务器端渲染变成了一件有吸引力的事情。 服务器端渲染除了要解决对浏览器环境的依赖,还要解决两个问题: 前后端可以共享代码 前后端路由可以统一处理 Rea

  • 如果你调研服务器端渲染(SSR)只是用来改善少数营销页面(例如/,/about,/contact等)的 SEO,那么你可能需要预渲染。无需使用 web 服务器实时动态编译 HTML,而是使用预渲染方式,在构建时(build time)简单地生成针对特定路由的静态 HTML 文件。优点是设置预渲染更简单,并可以将你的前端作为一个完全静态的站点。 如果你使用 webpack,你可以使用prerende

  • 我刚刚开始研究ReactJS,发现它提供了两种呈现页面的方法:服务器端和客户端。但是,我不明白如何一起使用它。构建应用程序有两种不同的方法,还是可以一起使用? 如果我们可以一起使用它,如何做到这一点——我们需要在服务器端和客户端复制相同的元素吗?或者,我们可以只在服务器上构建应用程序的静态部分,在客户端构建动态部分,而不与已经预渲染的服务器端建立任何连接吗?

  • 服务端渲染一个很常见的场景是当用户(或搜索引擎爬虫)第一次请求页面时,用它来做初始渲染。当服务器接收到请求后,它把需要的组件渲染成 HTML 字符串,然后把它返回给客户端(这里统指浏览器)。之后,客户端会接手渲染控制权。 下面我们使用 React 来做示例,对于支持服务端渲染的其它 view 框架,做法也是类似的。 服务端使用 Redux 当在服务器使用 Redux 渲染时,一定要在响应中包含应用