Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
July 11, 2021 12:45 pm GMT

Adding server-side-rendering to existing vue 3 project

In this article we will see how to add server side rendering support to existing vue 3 project.I will be using one of my existing vue3 & vuex project which is available in github.

First we have to add few dependencies && devdependencies so that our project can support ssr

yarn add @vue/server-renderer [email protected]
yarn add -D webpack-manifest-plugin webpack-node-externals express

NOTE: upgrading vue to latest version so that we can use onServerPrefetch lifecycle hook

for server-side-rendering we will have to create two different entry points(files) one, which will be used in server & another in client side also we will need to different build commands for server/client, lets add these two first in package.json scripts section

"build:client": "vue-cli-service build --dest dist/client","build:server": "VUE_APP_SSR=true vue-cli-service build --dest dist/server","build:ssr": "rm -rf ./dist && npm run build:client && npm run build:server"

we have added a flag VUE_APP_SSR=true which would help us for bundling server side and ignore any window logics as those won't work in server-side.There will be two separate directory within dist folder client && server having separate code.

With build scripts ready lets move to entry files of server side & client side, we will have a common main.ts file which will be included in both entry files entry-client.ts && entry-server.ts

Lets create main.ts, we have to take care of createApp && createSSRApp for respective entry points.we can make use of flag VUE_APP_SSR=true or typeof window check

const isSSR = typeof window === 'undefined';const app = (isSSR ? createSSRApp : createApp)(rootComponent)

At the end our file would look something like this

import { createSSRApp, createApp, h } from 'vue'import App from './App.vue'import router from './router';import { store } from './store'export default function () {  const isSSR = typeof window === 'undefined';  const rootComponent = {    render: () => h(App),    components: { App },  }const app = (isSSR ? createSSRApp : createApp)(rootComponent)  app.use(router);  app.use(store);  return {    app,    router,    store  };}

With the main crux ready lets create entry-client.ts && entry-server.ts

# entry-server.tsimport createApp from './main';export default function () {  const {    router,    app,    store  } = createApp();  return {    app,    router,    store  };}

In server entry file, we are just exporting app,router,store which would be used while serving via express

# entry-client.tsimport createApp from './main'declare let window: any;const { app, router, store } = createApp();(async (r, a, s) => {  const storeInitialState = window.INITIAL_DATA;  await r.isReady();  if (storeInitialState) {    s.replaceState(storeInitialState);  }  a.mount('#app', true);})(router, app, store);

window.INITIAL_DATA will hold the initialData that would be prefetched in server-side and would be stored in global window object, then in clientSide we will use this data to populate our store on first load.

Now,lets move to webpack config part of SSR, to work with webpack we have to create a vue.config.js file. we would include webpack-manifest-plugin,webpack-node-externals,webpack

const ManifestPlugin = require("webpack-manifest-plugin");const nodeExternals = require("webpack-node-externals");const webpack = require('webpack');const path = require('path');

Lets add config, i will be using export.chainWebpack directly to modify default webpack config provided by vue

exports.chainWebpack = webpackConfig => {   if (!process.env.VUE_APP_SSR) {    webpackConfig      .entry("app")      .clear()      .add("./src/entry-client.ts");    return;  }  webpackConfig    .entry("app")    .clear()    .add("./src/entry-server.ts");}

based on which build is going to run we have added different entry points, for this we will use VUE_APP_SSR flag.

Now we have to add few more code so that webpack can build server-side bundle properly.we have to set target to node && libraryFormat to commonjs2 since this file is going to run via express

  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");  webpackConfig.plugin('limit').use(    new webpack.optimize.LimitChunkCountPlugin({      maxChunks: 1    })  )

you can read more about this configuration on this SSRbuildConfig

the last part is to create an server.js file which we will run on server via express.

const path = require('path');const fs = require('fs');const serialize = require('serialize-javascript');const express = require('express');const { renderToString } = require("@vue/server-renderer");const  PORT = process.env.PORT || 4455const manifest = require("../dist/server/ssr-manifest.json");const appPath = path.join(__dirname, "../dist",'server', manifest["app.js"]);const App = require(appPath).default;const server = express();server.use("/img", express.static(path.join(__dirname, "../dist/client", "img")));server.use("/js", express.static(path.join(__dirname, "../dist/client", "js")));server.use("/manifest.json", express.static(path.join(__dirname, "../dist/client", "manifest.json")));server.use("/css", express.static(path.join(__dirname, "../dist/client", "css")));server.use(  "/favicon.ico",  express.static(path.join(__dirname, "../dist/client", "favicon.ico")));server.get('*', async (req, res) => {  const { app, router, store } = await App(req);  await router.push(req.url);  await router.isReady();  let appContent = await renderToString(app);  const renderState = `    <script>      window.INITIAL_DATA = ${serialize(store.state)}    </script>`;  fs.readFile(path.join(__dirname, '../dist/client/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);  });});server.listen(PORT, ()=>{  console.log(`server listening at port ${PORT}`)})

we will be using above code which will intercept all request to our server.

const manifest = require("../dist/server/ssr-manifest.json");const appPath = path.join(__dirname, "../dist",'server', manifest["app.js"]);
#ssr-manifest.json  "app.css": "/css/app.aaa5a7e8.css",  "app.js": "/js/app.b8f9c779.js",  "app.css.map": "/css/app.aaa5a7e8.css.map",  "app.js.map": "/js/app.b8f9c779.js.map",...

this is where we use manifest.json file to select appropriate server file that would be served from express, contents of this json file is an object which has mapping for specific bundles

await router.push(req.url);await router.isReady();let appContent = await renderToString(app);

above mentioned code will be used to match url-page properly with router.push, then renderToString will output everything as string which would be served from express.

In the above server.js you can see html variable holds the entire content that will be served from express to browser, next step would be to add support for meta-tags.

After all these configuration, now our pages can be rendered from server, now we will use axios to fetch data from endpoint which can rendered from server

# vue file    const fetchInitialData = async () => {      const response = await axios('https://jsonplaceholder.typicode.com/posts')      store.dispatch(AllActionTypes.USER_LISTS, response.data || [])    }    onServerPrefetch(async () => {     await fetchInitialData()    })    const listData = computed(() => {      return store.getters.getUserList || []    });    onMounted(async () => {      if(!listData.value.length){        await fetchInitialData();      }    })

The above code is an example of how can we fetch data for server-side rendering, we have used onServerPrefetch lifecycle method to fetch data && for client side we are using onMounted hook incase data is not available in window from server.

Note: I have skipped few steps while explaining, all code regarding this article is present at Repository.

Resources which helped me to create this article are
https://v3.vuejs.org/guide/ssr/introduction.html#what-is-server-side-rendering-ssr
youtube


Original Link: https://dev.to/shubhadip/adding-server-side-rendering-to-existing-vue-3-project-53oo

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To