Build-time rendering
Build-time rendering (BTR) renders a route to HTML during the build process and in-lines critical CSS and assets needed to display the initial view. This allows Dojo to pre-render the initial HTML used by a route and inject it directly into the page immediately, resulting in many of the same benefits of server side rendering (SSR) such as performance gains and search engine optimization without the complexities of SSR.
Using BTR
First make sure index.html
includes a DOM node with an id
attribute. This node will be used by Dojo’s virtual DOM to compare and render the application’s HTML. BTR requires this setup so it can render the HTML generated at build time. This creates a very fast and responsive initial rendering of the route.
index.html
<!DOCTYPE html>
<html lang="en-us">
<head>
<title>sample-app</title>
<meta name="theme-color" content="#222127" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<div id="app"></div>
</body>
</html>
The application should then be mounted to the specified DOM node:
main.ts
const r = renderer(() => w(App, {}));
const domNode = document.getElementById('app') as HTMLElement;
r.mount({ registry, domNode });
The project’s .dojorc
configuration file should then be updated with the id
of the root DOM node and routes to render at build time.
.dojorc
{
"build-app": {
"build-time-render": {
"root": "app",
"paths": [
"#home",
{
"path": "#comments/9999",
"match": ["#comments/.*"]
}
]
}
}
}
This configuration describes two routes. A home
route and a more complex comments
route. The comments
route is a more complex route with parameter data. A match
is used to make sure that the build-time HTML created for this route is applied to any route that matches the regular expression.
BTR generates a screenshot for each of the paths rendered during build in the ./output/info/screenshots
project directory.
History manager
Build time rendering supports applications that use either the @dojo/framework/routing/history/HashHistory
or @dojo/framework/routing/history/StateHistory
history managers. When using HashHistory
, ensure that all paths are prefixed with a #
character.
build-time-render
feature flag
Build time rendering exposes a build-time-render
feature flag that can be used to skip functionality that cannot be executed at build time. This can be used to avoid making fetch
calls to external systems and instead provide static data that can be used to create an initial render.
if (has('build-time-render')) {
const response = await fetch(/* remote JSON */);
return response.json();
} else {
return Promise.resolve({
/* predefined Object */
});
}
Dojo Blocks
Dojo provides a blocks system which can execute code in Node.js as part of the build time rendering process. The results from the execution are written to a cache that can then be transparently used in the same way at runtime in the browser. This opens up new opportunities to use operations that might be not possible or perform poorly in a browser.
For example, a Dojo Block module could read a group of markdown files, transform them into VNodes, and make them available to render in the application, all at build time. The result of this Dojo Block module is then cached into the application bundle for use at runtime in the browser.
A Dojo Block module gets used like any middleware or meta in a Dojo widget. For the Dojo build system to be able to identify and run a block module there are three requirements that must be met:
- The module must have a
.block
suffix, for examplesrc/readFile.block.ts
. - The Block must only have a single default export
- Return values from blocks (from a promise resolution or as an immediate return) must be serializable to json
Other than these requirements there is no configuration or alternative authoring pattern required.
For example, a block module could read a text file and return the content to the application.
src/readFile.block.ts
import * as fs from 'fs';
import { resolve } from 'path';
export default (path: string) => {
path = resolve(__dirname, path);
return fs.readFileSync(path, 'utf8');
};
src/widgets/MyBlockWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import block from '@dojo/framework/core/middleware/block';
import readFile from '../readFile.block';
const factory = create({ block });
export default factory(function MyBlockWidget({ middleware: { block } }) {
const message = block(readFile)('../content/hello-dojo-blocks.txt');
return <div>{message}</div>;
});
This widget runs the src/readFile.block.ts
module at build time to read the contents of the given file to be used in the widget’s render output.