DEV Community

Cover image for JavaScript SSR made easy
Ibrahim ben Salah
Ibrahim ben Salah

Posted on • Edited on

JavaScript SSR made easy

Server Side Rendering (SSR) in any JavaScript framework, with preservation of the reactivity behavior of the application, depends on the ability to hibernate the runtime state (at server), so that it can be later (at client) hydrated back to its previous state.

Video

https://www.youtube.com/watch?v=HXmWYlhQCeU
(PS, this is my first video in a decade, I hope my coding skills make up for the video quality ;)

Source code

kitchen-sink

The following code snippet is a basic SSR simulation of how hibernation and hydration could work.

// ~/pages/hello-world.ts
export default function server() {
      console.log("Hello World");

   function client() {
      console.log("Hello World");
   }

   return client;
}
Enter fullscreen mode Exit fullscreen mode

I was hoping I could use an existing solution for a UI library that I wrote called @xania/view but all solution that I could find are specific to other UI frameworks.
So I decided to build a UI agnostic solution. It is extremely hard and it evolves writing code transpiler/compiler but I did it anyway and I am hyped to share with you the results.

Introducing vite-plugin-resumable

This plugin is a UI framework agnostic solution for hibernating and hydrating javascript closures. You can say we found a way to serialize functions. It is the foundation for SSR feature of @xania/view but it can be used with any other UI library.

Continue reading to learn how to setup your project or visit github here for full working source code of the example.

Setup

Currently we have only support for vite projects.

  1. create new vite project

    npm init vite my-vite-app

  2. install resumable package

    npm i vite-plugin-resumable

  3. next step is to add the plugin to your vite configuration

import resumable from "vite-plugin-resumable"

  ...
  plugins: [
    resumable();
  ]
  ...

Enter fullscreen mode Exit fullscreen mode

By default, this plugin looks in pages folder for all files with .tsx extension and creates a server entry for each file found.

So go ahead and create a page called hello-world.tsx script in pages folder


// ~/pages/hello-world.ts
export function view() {
   console.log("Hello server");
   const counter = new State(1);
   counter.subscribe({
     next(value) { 
       console.log("counter", value);  
     }
   });

   function client() { 
      console.log("Hello client");
      document.body.addEventListener("click", 
          () => counter.set(x => x + 1));
   }

   return client;
}
Enter fullscreen mode Exit fullscreen mode

The resumable package will automatically infer the client code from the result of the server entry function.
In this case the client code is generated from the function declaration of client. This function will be executed on client and will use the hydrated instance of the counter object, including the registered next-observer.

Let's start the application and see this in action

npm start

Open a browser and navigate to http://localhost:5173/hello-world

// terminal output
Hello server
Enter fullscreen mode Exit fullscreen mode
// browser output
Hello client
Enter fullscreen mode Exit fullscreen mode

Clicking on the document will increment the counter and trigger the observer which will print current value to the console of your browser.

// browser output
counter, 2
Enter fullscreen mode Exit fullscreen mode

Conclusion

This plugin streamlines the development process by simplifying the code structure required to make a web application that can be executed on both the server and client side.

This is currently an alpha (aka proof of concept) version to verify usefulness of this approach. So far, this plugin captures the generic features of SSR so that any UI library could benefit from it.

We optimize the amount of client script that is sent to the client, but in the future we will be looking for more opportunities to optimize the state data (e.g. if we only access a property of an object then we may want to sent only the value of that property instead of the whole object.

next is to try this out with React and I will also build an adapter for @xania/view

Top comments (2)

Collapse
 
beeplin profile image
Beep LIN

I guess qwik's way is somehow helpful?

Collapse
 
xania profile image
Ibrahim ben Salah

qwik was a big inspiration but I didnt like the component$ primitives in qwik, xania infers the client code from the return expression instead.
Another more significant difference is that qwik does not allow for closures to close over instance of State class as I demonstrated with xania.