I have shown how to quickly restart a web application from last known markup saved in localStorage in Hydrate your apps blog post. In this blog post I will show how to do the same at the very first time the web application loads. We are going to use the web application itself to generate the initial HTML markup during the build step, and then we are going to "rehydrate" the markup when the application starts in the user's browser. This avoids a flash of a blank content or a spinning loader common in many web apps.
Typical web application startup
Look at a typical web application startup timeline below (you can see this live)
The browser loads the initial base page, then the framework and application code. Once the web application starts it fills the blank page with the actual content. The user sees blank space until the web application starts. If we inspect the page's markup, it is empty at the beginning
1 | <body> |
The application fills the "div#app" element with the actual content
1 | <body> |
Can we do better? In some cases, we do know the initial data that our application will render at build time. Let us prebuild the HTML markup and serve it right away, and the web application will have time to start and then enhance the static markup with live behavior (I call this enhancement "hydration")
Prebuilt web application startup
If we are showing a Todo application with a set of initial items, we could run the web application at build time (some frameworks make it easier than others, for example virtual-dom makes it very simple), capture the produced HTML and put it into the "index.html".
You can find this source code in bahmutov/hydrate-vdom-todo. We are going to start by looking at the web application main rendering loop - and it is very simple.
See the full file at src/app.js
1 | const Todos = require('./todos') // data = Model |
This is pretty standard virtual dom rendering loop. The great thing about the first 3 code lines - they do not require a live browser and produce "virtual" markup from actual data, and we can easily convert it to HTML at build time.
Thus as part of my build in addition to running webpack, I also run hydrate-app.js that is extremely simple. It takes the empty index.html and fills the web application's div with the initial HTML
1 | // Model |
The dist/index.html has nice static HTML in its application div
1 | <body> |
The browser now shows the correct information immediately.
Hydrating the page
We now get the initial web application markup immediately because it is part of the page.
How do we "hydrate" or tell our application that it should just extend the existing markup?
By default, the rendering loop tries to create a new root element from the VirtualNode
1 | // start render |
Instead of using createElement
that takes VirtualNode
instance and returns new
DOM element, we can grab the DOM element from the page and create a VirtualNode
!
I am using helper library html-to-vdom that
does this.
1 | // grab DOM element already present |
That is it, now that we created a VirtualDom
from the actual HTML we can
patch it using normal VirtualDom diff
methods.
1 | function renderApp () { |
This is how a Virtual-Dom application can start without destroying and recreating a part of the page.
Results
You can check out the live result, and most importantly profile the timeline.
Notice that the Todo contents is shown right away - the application code is non-blocking at the bottom of the body, the markup is available, and the user sees the list at startup. The application once again starts at 1 second mark, but the content does not flash or flicker - because the virtual dom only adds a few patches to the HTML already there; all the patches are just setting up the event handlers!
A note of caution
The true web application should still start quickly. The initial markup might be there, but it is static - there are no event handlers, and the user might try to interact with the page only to find that it is unresponsive. One can allow the page to be used in the read-only mode - the user should be able to scroll, maybe go to the outside resources via A links, and so on.
Further reading
Some frameworks make it simple to render HTML and state (model) to hydrate the page and quickly start. For example Virtual-Dom and React make it simple to generate static HTML on the server and hydrate on the client.
React implementation
You can see how React can generate custom HTML markup and hydrate the application at every server request in this universal example. Each HTTP GET to the page runs the application code and generates 2 things: the HTML and the data store.
1 | app.use(handleRender) |
We send both the HTML and the finalState
; the state being a simple JSON object
in the page
1 | function renderFullPage(html, initialState) { |
The client code can grab the state and update the HTML to start running
1 | const initialState = window.__INITIAL_STATE__ |