Velo: Server Side Rendering and Warmup Data APIs
Tags: wix, velo, velobywix, javascript
Published: 2022-07-22 12:47:43

_Learn how to optimize data receiving and reduce the Wix site load time_

In Velo, we use the `$w.onReady()` method as a start point for interacting with the page. This method ensures that all the page elements have finished loading and we can interact with them. The life cycle of the Velo site includes two runs of the `$w.onReady()` method.

The first run of the `$w.onReady()` callback happens on the server-side when the server builds the HTML page. The server executes a Velo code and puts a result into HTML (if it's possible).

The second run goes on the client-side in the browser when a site page has loaded.

_**Velo API Reference:**_ [Rendering](https://www.wix.com/velo/reference/wix-window/rendering-obj/introduction)

> When possible, the rendering process is split in two in order to improve performance. The first cycle in the process happens in the server-side code and the second cycle happens in the client-side code. If not possible on the server-side, all rendering happens client-side.

Let's playing with SSR for understanding how it works.

For example, we have the below code:

```js
$w.onReady(function () {
  $w('#text1').text = 'Hello!';
});
```

Code will be executed on the server-side then a result will be added to the HTML page. And the page will send to the client-side with inserted data.

## Rendering API

We can control the step of the render cycle with [`wixWindow.rendering.env` API](https://www.wix.com/velo/reference/wix-window/rendering-obj/env).

`env` property returns `backend` when rendering on the server-side and `browser` when rendering on the client-side.

Let's update the code to see it. It's a string with env value and timestamp.

```js
import { rendering } from 'wix-window';

$w.onReady(function () {
  $w('#text1').text = `${rendering.env}: ${Date.now()}`;
});
```

Now, when we reload the page we can see that HTML content has `backend` value. When the page finished loading then we see the `browser` value, it's the second run of `$w.onReady()` on the client-side updates a value of text.

**SSR & browser runtime**

<video controls="" loop="" preload="metadata" src="https://shoonia.site/assets/videos/ssr.mp4" type="video/mp4"></video>

It looks easy.

## Asynchronous operation

What about async operations?

If we want to add SSR with some async operation, we should wait for the promise to be [fulfilled](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#:~:text=A%20Promise%20is%20in,that%20the%20operation%20failed.).

Let's have a look at an example. Creates a query for retrieving items from a database and prints them as a string.

```js
import wixData from 'wix-data';

$w.onReady(function () {
  // Async request to database
  wixData.query('goods').find()
    .then((data) => {
      $w('#text1').text = JSON.stringify(data.items);
    });
});
```

As we can see, the SSR doesn't work with any async operations. When we reload the page, we see a default text that the Text element contains in the editor. And after a while, we see database items. It's the second run of the `$w.onReady()` callback on the client-side.

**A site without server-side render with dynamic data**

<video controls="" loop="" preload="metadata" src="https://shoonia.site/assets/videos/no-ssr.mp4" type="video/mp4"></video>

_I'm using the throttling of the network in Chrome DevTools to reduce Internet speed. It may be helpful for debugging._

It happened because `$w.onReady()` doesn't wait for a promise fulfilled on the server-side. The server doesn't wait for a query result and sends the HTML page with default content.

To fix is very simple, we should wait for a promise result. The `$w.onReady()` supports the async callback functions. Let's update the code with [`async/await`](https://javascript.info/async-await) operators.

```js
import wixData from 'wix-data';

$w.onReady(async function () {
  const data = await wixData.query('goods').find();

  $w('#text1').text = JSON.stringify(data.items);
});
```

Now, we can see the SSR starts to work. And the server has rendered the HTML page with the database items.

**A site with server-side render for dynamic data**

<video controls="" loop="" preload="metadata" src="https://shoonia.site/assets/videos/with-ssr.mp4" type="video/mp4"></video>

> ❗ don't forget to turn off the throttling of the network after testing 😉

### Long async calls slow down site performance

We should be careful using `$w.onReady()` with an async callback. Long async tasks slow down the render the the page.

For example, we add a promise with a delay of 5 seconds into the callback.

```js
$w.onReady(async function () {
  // a delay for 5 seconds
  await new Promise((r) => setTimeout(r, 5000));

  $w('#text1').text = Date.now().toString();
});
```

If we run this code, we will see that the server will wait for 5 seconds to complete. And after the HTML page has loaded on the client, we again wait for 5 seconds before seeing the result.

We wait twice for the promise to be fulfilled on the server and on the client.

<img alt="chrome network inspector" height="446" loading="lazy" src="https://d33wubrfki0l68.cloudfront.net/14d1f756237bd62b54790685d7bf549271e64c06/b5e61/assets/images/network-inspector.jpg" width="1000">

_Network inspector, time to load the HTML page from the server with a delay of 5 seconds_

## The Warmup Data API

Using the Warmup Data API, we are able to transfer data with a page code from the server and read this data on the client.

**Velo API Reference:**

> The Warmup Data API is used to optimize data loading for sites that render both on the server and in the browser, allowing costly data fetching operations to be done only once.

Velo: [The Warmup Data API](https://www.wix.com/velo/reference/wix-window/warmupdata-obj) example

```js
import { warmupData, rendering } from 'wix-window';

// Set data on the server-side
if (rendering.env === 'backend') {
  warmupData.set('my-key', 'server data');
}

// Get data on the client-side
if (rendering.env === 'browser') {
  const data = warmupData.get('my-key');

  console.log(data); // -> "server data"
}
```

We can use the Warmup Data to reduce requests to a database. There we save the query response to `warmupData` on the server and read it on the client without additional database request.

### Implement Warmup Data util function

We'll implement a feature that will enable server-side rendering and use the Warmup Data to prevent second data request on the client-side, it reduces a time of waiting.

Create a file for util function.

**Add file to public section on the sidebar**

```
public
└── warmupUtil.js
```

It is a wrapper function. It has two arguments:

- First argument - _key_: It's a unique key corresponding to data for the Warmup Data
- Second argument - _func_: It's an async function which result we want to use with the Warmup Data.

**public/warmupUtil.js**

```js
import { warmupData, rendering } from 'wix-window';

export const warmupUtil = async (key, func) => {
  // On the server-side
  if (rendering.env === 'backend') {
    // Get data
    const data = await func();

    // Set the warmup data on the server-side
    warmupData.set(key, data);

    return data;
  }

  // On the client-side

  // Get the warmup data on the client-side
  const data = warmupData.get(key);

  // Checking a cached data exist
  if (data) {
    return data;
  }

  // If we don't have cache data from the server,
  // then we do a backup call on the client
  return func();
};
```

On the server, it waits for the async function result and sets it to the Warmup Data.

On the client, it uses data from the Warmup Data. If it has no data (some glitch on the server), it will call _func_ on the client.

## Parallel execution for a few async tasks

We should remember the `$w.onReady()` effect of page loading. If we want to use a few async functions in `$w.onReady()` callback then we should avoid using them in queue one by one.

For example, if each of these async functions executes at 100 milliseconds, the `$w.onReady()` will need to wait for 300 milliseconds for complete execution all of them.

```js
// ❌ wrong approach!!
$w.onReady(async function () {
  const one = await warmupUtil('one-async-func', oneAsyncFunc); // ⏳ 100 ms
  const two = await warmupUtil('two-async-func', twoAsyncFunc); // ⏳ 100 ms
  const three = await warmupUtil('three-async-func', threeAsyncFunc); // ⏳ 100 ms

  // ⏳ wait one by one (100 ms * 3) = 300 ms

  $w('#text1').text = JSON.stringify({ one, two, three });
});
```

We are able to aggregate a bunch of promises with [`Promise.all()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all), execute them in parallel, and wait until all of them are ready.

```js
// ✅ parallel asynchronous execution
$w.onReady(async function () {
  const [one, two, three] = await Promise.all([
    warmupUtil('one-async-func', oneAsyncFunc),
    warmupUtil('two-async-func', twoAsyncFunc),
    warmupUtil('three-async-func', threeAsyncFunc),
  ]);

  // ⏳ wait 100 ms. Parallel execution of all promises

  $w('#text1').text = JSON.stringify({ one, two, three });
});
```

##Code Snippets

Here is a code snippet with JSDoc annotation. And an example of use.

**public/warmupUtil.js**

```js
import { warmupData, rendering } from 'wix-window';

/**
 * @template T
 * @param {string} key
 * @param {() => Promise<T>} func
 * @returns {Promise<T>}
 */
export const warmupUtil = async (key, func) => {
  if (rendering.env === 'backend') {
    const data = await func();

    warmupData.set(key, data);

    return data;
  }

  const data = warmupData.get(key);

  if (data) {
    return data;
  }

  return func();
};
```

**Page Code Tab**

```js
import { warmupUtil } from 'public/warmupUtil';

const getGoods = async () => {
   const { items } = await wixData.query('goods').find();

   return items;
};

$w.onReady(async function () {
  const items = await warmupUtil('goods-items', getGoods);

  $w('#text1').text = JSON.stringify(items);
});
```

## Resources

- [Rendering `env` API](https://www.wix.com/velo/reference/wix-window/rendering-obj/env)
- [Warmup Data API](https://www.wix.com/velo/reference/wix-window/warmupdata-obj)
- [Velo: About the Page Rendering Process](https://support.wix.com/en/article/velo-about-the-page-rendering-process)

## Posts

- [Add hotkeys to Wix site](https://shoonia.site/hotkeys-custom-element/ )
- [Global type definitions](https://shoonia.site/global-type-definitions-in-velo/)
- [Type safety your code with JSDoc](https://shoonia.site/type-safety-your-code-with-jsdoc/)
- [Repeated item event handlers v2.0](https://shoonia.site/repeated-item-event-handlers-v2/)