Hydration
Hydration is the process of making the initial pre-rendered html of a website interactive.
This page is only relevant when using static or server mode.
While pre-rendering your components on the server (or at built time with 'static' mode) allows for a fast "first contentful paint" (when useful content is first displayed to the user), the site is not interactive (e.g. responding to button clicks) until the client-side rendering is started and event handlers have been attached.
In static and server mode your apps lifecycle always starts on the server, which builds your components once and render
them to html. Then when the browser has loaded your site along with additional files like .js
or images, your app is executed
again on the client to continue rendering on the client. This "picking up rendering on the client" is called Hydration.
The terms "Pre-Rendering" and "Server-Side-Rendering" can be use pretty much interchangeably. "Server" in this context just refers to whatever computer performs the initial rendering away from the user's browser. This may be an actual webserver running in some datacenter, but when using "static" mode this can also be just your computer or e.g. the machine running your ci pipeline.
Hydration Setup
For hydration to happen seamlessly the initial client-side render has to match the previously pre-rendered html from the server exactly. This is achieved by re-executing the same components that have previously pre-rendered the html on the server also on the client as soon as the browser loads the page.
With Jaspr you can handle hydration in two ways:
- manually by writing both server and client entrypoints separately, or
- automatically by using the
@client
annotation.
It is generally recommended to use automatic hydration.
Automatic Hydration (Recommended)
For automatic hydration simply use the @client
annotation on any component.
A component annotated with @client
will be automatically hydrated on the client after it has been pre-rendered. In principle,
this is like 'resuming' the rendering for a component on the client and picking up where the server-side rendering has left off.
Read more about @client
components and how to best use them here.
Manual Hydration
With manual hydration, you need to create a separate client entrypoint for your app inside the web/
directory:
// Client-specific import
import 'package:jaspr/browser.dart';
// Our main component
import 'lib/app.dart';
void main() {
// Attaches the app component to the <body> tag
// and hydrates the component / makes it interactive.
runApp(App(), attachTo: 'body');
}
This entrypoint, as any .dart
file, will then be compiled to javascript as <filename>.dart.js
.
To load this compiled javascript file on the client, include it as a <source>
element in your server-rendered html:
// Server-specific import
import 'package:jaspr/server.dart';
// Our main component
import 'app.dart';
import 'jaspr_options.dart';
void main() {
Jaspr.initializeApp(options: defaultJasprOptions);
runApp(Document(
head: [
// Links to the compiled `web/main.dart` file.
script(src: 'main.dart.js', []),
],
// Pre-renders the [App] component inside the <body> tag
body: App(),
));
}
The above setup mounts the App()
component directly inside the <body>
element. However, when you are building a more
content-heavy or mostly static website (static meaning without much user interaction)
you probably don't need to ship your whole app structure to the client, but rather want only certain parts of your
app to be interactive.
You can choose which part(s) of your app you want to hydrate by mounting only that part in your client entrypoint.
Additionally on the client, you can call runApp()
multiple times to mount different parts of your app separately:
Assuming you have a page layout like this:
<body>
<header>...</header>
<main>
<div id="content">...</div>
<div id="sidebar">...</div>
</main>
<footer>...</footer>
</body>
Your client entrypoint could be:
void main() {
runApp(Header(), attachTo: 'header');
runApp(Sidebar(), attachTo: '#sidebar');
runApp(Content(), attachTo: '#content');
}
This will run three apps simultaneously, attached to the specified root elements using css selectors.
The advantage of this approach is that you can leave other parts of your app, e.g. a static footer, out of the bundled javascript and thereby reducing loading and startup time.
Be aware that on the server, you must still construct the complete app layout and and render the targeted island components manually at the right location.