Client Side Rendering
Once the browser loads the page that was returned by server side rendering, it will start to asynchronously load malibu javascript, and call startApp()
, which executes the following steps.
- First, a part of the application that can be hydrated immediately is rendered with preRenderApplication.
- Data for hydrating the main component is fetched from /route-data.json
- Finally, the main part of the page is hydrated with renderApplication.
The call to startApp can be found in app/client/app.js.
This document assumes a reasonable understanding of how hydration works in React.
preRenderApplication
preRenderApplication can be used to hydrate any component that can be loaded with just data that is present in the HTML dom. The preRenderApplication function can be found in app/client/render.js.
Examples of this would be the Header, BreakingNews components, the latter of which directly fetches it’s data from the backend every 30 seconds.
During the preRenderApplication phase, the redux store is bootstrapped from data available in a script tag, with id “initial-page”. This is put on the page in the layout at view/pages/layout.ejs.
Fetch data from /route-data.json
Browser pages get their information from /route-data.json, which passes the current path as a query parameter. For example, if the browser is currently on /?p=q
, then the data for that page would come from /route-data.json?path=%2F&p=q
.
Internally, /route-data.json works by calling loadData, and returning the entire data back as JSON.
Depending on the returned status, the browser behaves accordingly:
- If the status code is 200, and httpStatusCode is also 200, then the page is rendered with renderApplication
- If the status code is 200, but httpStatusCode is 301 / 302, then the browser derirects with window.location
- If the status code is 404, then the browser may try to load the page again by bypassing the service worker (setting #bypass-sw in the anchor). If the page is still not found, the browser will have rendered a 404 page.
renderApplication
renderApplication is finally used to hydrate the IsomorphicComponent and other components in the application. It can be found in app/client/app.js.
Once /route-data.json returns with the relevant data for a given page, the redux store is update with the new data. Any components that were rendered during preRenderApplication will update if there is a difference between the settings they were created with (usually there will not be any changes).
Finally, the main part of the page is hydrated using renderIsomorphicComponent. This proceeds similar to the render phase of server side rendering, using pickComponent.
Ajax Navigation
Once the page is hydrated, navigation between pages can happen via AJAX, instead of refreshing the entire page. When a link to a page is created using the Link component, then it will load via AJAX.
This works by calling navigateTo(path), which simply calls /route-data.json for the new path, then updates the redux store. IsomorphicComponent will detect that the redux store has updated, and will swap out the old page for the new page (updating history and analytics in the process). If the new page was of a different pageType, then the new component is loaded before the page is swapped out.
Progressive Web App
When a visitor comes to your app for the first time, a ServiceWorker is installed in the background. ServiceWorkers act as a proxy between your browser and all outgoing network. The malibu ServiceWorker does the following.
- First a list of important assets are downloaded and cached. This allows the app to work offline.
- The list of routes that can be handled offline is registed with the help of Google Workbox. The routes here are the same as those generated by the generateRoutes function described in the routing section
- Third party ServiceWorker functionality, such as push notifications, can be installed via importScript.
If the user enters your app via one of the routes listed, then the initial request will not go to the server. Instead, the user will be served a shell, containing an empty body. Typically, a good shell will have a fully functioning header and menu, basic seo tags and some sort of indicator to show that it is loading the data. The shell should load instantly, and then immediately begin the process of initializing the page as described in the start of this document (with the only difference being that the main render does an actual render() instead of hydrate()).
If the application is loaded via the shell, then window.qtLoadedFromShell will be set to true.
If your application also has a manifest.json that allows installation, then the user will be prompted to install the PWA. However, the functionality described in this section applies to both PWA and regular browsers. If you would like to disable ServiceWorker installation, see app/client/app.js.
The ServiceWorker can be found in views/js/service-worker.ejs, though it should be configured in app/client/serviceWorkerHelper.sjs.