DEV Community

Akbar Nafisa
Akbar Nafisa

Posted on

My first time implementing SSR using Vue 3 and Vite.

Server-side Rendering (SSR) is one of the methods to present web content to the user. Even though this method is quite old, I never had a chance to learn it by a code, only by the concept. In this article, I will try to provide an overview of SSR, with the goal of demonstrating its implementation through simple examples.

What is SSR

SSR refers to generating content in the browser while the fetching and rendering are done on the server. Since the content is rendered on the server, it becomes available to the user once the loading is complete. However, for any interaction process, we need to handle it by doing the hydration process first. The HTML that the user receives is completely static, and there is a waiting time for the hydration process. This is called Time to Interactive. You can read the explanation from the web.dev team for more information here.

SSR in Vue

Nuxt is a popular framework for handling SSR project. However, in this article, we won’t use Nuxt to implement the SSR. Instead, we will use:

  • Vue 3 as the base client library
  • Express for the back end
  • Vite for the bundler

How to implement

For this article, we will use Stackblitz to implement our small project. You can check the full code here

Initiate the Project

Let’s initiate the project by using Vite vue-ts. Here is our directory, we will remove some files that we do not need.

Image description

Image description

After that, we will add these files



- index.html
- server.js # Main application server
- src/
  - main.ts          # we will store the function to init our app here
  - entry-client.ts  # This function mounts the app to a DOM element and will be used in the hydration process. 
  - entry-server.ts  # renders the app using the framework's SSR API, we will use it in server.js


Enter fullscreen mode Exit fullscreen mode

Add Client Code

For our client-side code, we add main.js. The createSSRApp is a function that was introduced in Vue 3 to create a server-side rendering application. This function can be used to render the Vue application on the server.



import { createSSRApp } from 'vue';
import App from './App.vue';

export const createApp = () => {
  /**
   * use createSSRApp to render the Vue App on the server
   * and send it to the user to do the hydration process
   */
  const app = createSSRApp(App);
  return {
    app,
  };
};


Enter fullscreen mode Exit fullscreen mode

Then we add entry-client.ts to initiate our app on the client-side



import { createApp } from './main.js';

/**
 * initiate the Vue App for a client-side application
 */
const { app } = createApp();
app.mount('#app');


Enter fullscreen mode Exit fullscreen mode

Also, let’s update the App.vue. Here we display a counter inside the button, which increments with every click.



<template>
  <div>
    <button @click="handleIncrement">{{ count }}</button>
  </div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const count = ref(1);
const handleIncrement = () => {
  count.value += 1;
};
</script>


Enter fullscreen mode Exit fullscreen mode

Add Server Code

The next step is to handle the server-side code. Add entry-server.ts and use the app from createSSRApp. Then, render the app to HTML using renderToString, which can then be sent to the client.



import { renderToString } from 'vue/server-renderer';
import { createApp } from './main';

/**
 * initiate the Vue App for a server-side application,
 * we use renderToString to render the app to HTML
 */

export const render = async () => {
  const { app } = createApp();
  const html = await renderToString(app);

  return {
    html,
  };
};


Enter fullscreen mode Exit fullscreen mode

Combine Client and Server

Now let’s handle the server. We will use express for our Node.js app



npm install express


Enter fullscreen mode Exit fullscreen mode

Next, add server.js. You can find this code guide in Vite SSR guide.



const express = require('express');
const fs = require('fs');
const path = require('path');
const { createServer } = require('vite');

async function initServer() {
  const app = express();

  // Create Vite server in middleware mode and configure the app type as
  // 'custom', disabling Vite's own HTML serving logic so parent server
  // can take control
  const vite = await createServer({
    server: { middlewareMode: true },
    appType: 'custom',
  });

  // Use vite's connect instance as middleware. If you use your own
  // express router (express.Router()), you should use router.use
  app.use(vite.middlewares);
  app.use('*', async (req, res) => {
    // 1. Read index.html
    let template = fs.readFileSync(
      path.resolve(__dirname, 'index.html'),
      'utf-8'
    );

    // 2. Apply Vite HTML transforms. This injects the Vite HMR client,
    //    and also applies HTML transforms from Vite plugins, e.g. global
    //    preambles from @vitejs/plugin-react
    template = await vite.transformIndexHtml(req.originalUrl, template);

    // 3. Load the server entry. ssrLoadModule automatically transforms
    //    ESM source code to be usable in Node.js! There is no bundling
    //    required, and provides efficient invalidation similar to HMR.
    const render = (await vite.ssrLoadModule('/src/entry-server.ts')).render;

    // 4. render the app HTML. This assumes entry-server.js's exported
    //     `render` function calls appropriate framework SSR APIs,
    //    e.g. ReactDOMServer.renderToString()
    const { html: appHtml } = await render();

    // 5. Inject the app-rendered HTML into the template.
    const html = template.replace('<!--main-app-->', appHtml);

    // 6. Send the rendered HTML back.
    res.set({ 'Content-Type': 'text/html' }).end(html);
  });

  return app;
}

initServer().then((app) =>
  app.listen(3000, () => {
    console.log('ready');
  })
);


Enter fullscreen mode Exit fullscreen mode

Then, let’s update our index.html. Add the placeholder <!--main-app--> and update the script source file to /src/entry-client.ts



<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
  </head>
  <body>
    <!-- 1. Add "main-app", it will be replaced by our App  -->
    <div id="app"><!--main-app--></div>

    <!-- 2. Update the source to entry-client.ts -->
    <script type="module" src="/src/entry-client.ts"></script>
  </body>
</html>


Enter fullscreen mode Exit fullscreen mode

Finally, update the dev scripts in package.json to node server.js



{
"scripts": {
"dev": "node server.js"
},
}

Enter fullscreen mode Exit fullscreen mode




Conclusion

In this article, we already learn how to create simple SSR applications by using Vue, Vite, and Express. There are a lot of things that we can improve, some of them are:

Top comments (2)

Collapse
 
przemek_bartecki_pl profile image
Przemek Bartecki

Hi,
In section "Add Client Code" do I have to create file main.js or it is mistake and should I add data to existing file main.ts