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.

@client components also have other features that are not easily replicable with manual hydration, such as passing data from server to client or hydrating components dynamically based on server-side state.

Read more about @client components and how to best use them here.

Manual Hydration

With manual hydration, you build a separate component tree in your client entrypoint that needs to match your server-rendered HTML.

In the simplest form, you can mount the same App component you use on the server.

lib/main.client.dart
// Client-specific import
import 'package:jaspr/client.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 has the same effect as adding @client to App but forces it to be rendered directly inside the <body> element.

The above approach mounts your whole app on the client. 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. This can be done in Jaspr by using the SlottedChildView component.

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:

lib/main.client.dart
void main() {
  runApp(SlottedChildView(slots: [
    ChildSlot.fromQuery('header', child: Header()),
    ChildSlot.fromQuery('#sidebar', child: Sidebar()),
    ChildSlot.fromQuery('#content', child: Content()),
  ]));
}

This will attach the child components to the specified 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 components manually at the right location. Keeping your server and client component tree in sync like this is generally cumbersome and error-prone, therefore it is recommended to use @client components instead of manual hydration.