How to SSR

In the most simple case, server side rendering in Percy boils down to rendering your virtual DOM to a String and responding to a client with that String.

use percy_dom::prelude::*;
use std::cell::Cell;

fn main () {
  let count_cell = Cell::new(5);

  let app = html! {
    <div id="app">
      <button onclick=|_ev| { *count+= 1; }>
        Hello world
      </button>
    </div>
  };


  let html_to_serve = app.to_string();
  // <div id="app"><button>Hello world</button></div>

  // .. server string to client (http response) ...
}

Hydrating initial state

You'll usually want your views to be rendered based on some application state. So, typically, your server will

  1. Receive a request from the client
  2. Set the initial application state based on the request
  3. Render the application using the initial state
  4. Reply with the initial HTML and the initial state
  5. Client takes over rendering, starting from the initial state.

To illustrate we'll take a look at an excerpt from a more realistic server side rendering example.

Afterwards you can check out the full example at examples/isormorphic.


A more realistic server side rendering implementation would look like the following:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" type="text/css" href="/static/app.css"/>
    <link rel="shortcut icon" href="#" />
    <title>Rust Web App</title>
</head>
<body style='margin: 0; padding: 0; width: 100%; height: 100%;'>
  <div id="isomorphic-rust-web-app" style='width: 100%; height: 100%;'>
      #HTML_INSERTED_HERE_BY_SERVER#
  </div>
  <script>
    function downloadJson(path, callback) {
      fetch(path)
        .then(function(response) {
          return response.json();
        })
        .then(function(json) {
            callback(json);
        });
    }
  </script>
  <script type=module>
    let client
    let updateScheduled = false

    window.GlobalJS = function () {}
    // TODO:
    // https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Window.html#method.request_animation_frame
    window.GlobalJS.prototype.update = function () {
      if (!updateScheduled) {
        requestAnimationFrame(() => {
          client.render()

          updateScheduled = false
        })
      }

      updateScheduled = true
    }
    window.global_js = new GlobalJS()

    import { Client, default as init } from '/static/isomorphic_client.js';

    async function run() {
      await init('/static/isomorphic_client_bg.wasm');

      client = new Client(window.initialState)
    }

    run();
  </script>
  <script>
      window.initialState = '#INITIAL_STATE_JSON#'
  </script>
</body>
</html>

#![allow(unused)]
fn main() {
// examples/isormorphic/server/src/rocket_server.rs
// Check out the full application in /examples/isormorphic directory

{{#include ../../../../examples/isomorphic/server/src/rocket_server.rs}}
}

And then the client would use serde to deserialize the initialState into a State struct and begin rendering using that State.