Hydration
Hydration in Pulse is opt-in and minimal. Omit hydrate and zero JavaScript is sent to the browser. Add it and Pulse binds events to the server-rendered HTML without re-rendering it — the SSR-painted content is preserved exactly as the server sent it.
Enabling hydration
Set the hydrate field to a browser-importable path to your spec file. Pulse uses this to generate the bootstrap script that mounts the client runtime:
export default {
route: '/counter',
hydrate: '/src/pages/counter.js', // browser path to this file
state: { count: 0 },
mutations: {
increment: (state) => ({ count: state.count + 1 }),
decrement: (state) => ({ count: state.count - 1 }),
},
view: (state) => `
<div>
<button data-event="decrement">-</button>
<span>${state.count}</span>
<button data-event="increment">+</button>
</div>
`,
}
Development bootstrap
In development (when hydrate is a source file path, not a /dist/ bundle), Pulse emits an inline bootstrap script:
<"tok-fn">class="tok-kw">script "tok-fn">type="module">
import spec from '/src/pages/counter.js'
import { mount } from '/src/runtime/index.js'
import { initNavigation } from '/src/runtime/navigate.js'
mount(spec, root, window.__PULSE_SERVER__ || {}, { ssr: true })
initNavigation(root, mount)
</"tok-fn">class="tok-kw">script>
This imports the spec and runtime source files directly — no build step required for development.
Production bundles
Run npm run build to generate production bundles. This creates content-hashed files in public/dist/ and a manifest.json mapping spec hydrate paths to bundle paths.
# Generated by npm run build
public/dist/
runtime-abc123.js # shared runtime (~2.1 kB brotli)
counter.boot-def456.js # per-page spec bundle (~0.5 kB brotli)
manifest.json # { '/src/pages/counter.js': '/dist/counter.boot-def456.js' }
When Pulse detects a manifest (via staticDir auto-detection or explicit manifest option), it resolves the hydrate path to the bundle path and emits a single <script src> tag instead of the inline bootstrap:
<"tok-fn">class="tok-kw">script "tok-fn">type="module" "tok-fn">src="/dist/counter.boot-def456.js"></"tok-fn">class="tok-kw">script>
The { ssr: true } option
The bootstrap script calls mount(spec, root, serverState, { ssr: true }). This tells the runtime to skip the initial re-render and bind event listeners to the existing DOM only.
This is what keeps LCP fast. The server-painted HTML is the LCP element. The JavaScript binds events without touching the DOM — no flash, no layout shift, no JS-rendered replacement.
{ ssr: false } on a server-rendered page. It re-renders the entire DOM on mount, replacing the server-painted HTML — causing a visible flash and pushing LCP to 400–600ms.mount()
The mount function attaches the Pulse runtime to a DOM element:
import { mount } from '@invisibleloop/pulse/runtime'
mount(
spec, // the page spec
rootEl, // the DOM element to mount into
serverState, // window.__PULSE_SERVER__ (server data from SSR)
{ ssr: true } // skip re-render on first mount
)
After mount, all data-event and data-action attributes in the DOM are wired to the spec's mutations and actions. State updates trigger a full view re-render via innerHTML replacement.
Pages without hydration
Omit hydrate and Pulse sends zero JavaScript to the browser — no runtime overhead, no hydration cost. This is the correct default for:
- Documentation pages
- Marketing/landing pages
- Blog posts and articles
- Any page with no client-side interactivity
hydrate and only add it when actual client interactivity is needed. Many pages that appear to need JavaScript can be handled server-side with routing and server data.Passing server state to the client
Server data fetched via server.data() is serialised into the page HTML as window.__PULSE_SERVER__. The client runtime reads this on mount, making server data available to the view during client re-renders without an additional network request.
// Emitted in the page HTML
<"tok-fn">class="tok-kw">script "tok-fn">id="__PULSE_SERVER__" "tok-fn">type="application/json">
{"product":{"id":1,"name":"Widget","price":9.99}}
</"tok-fn">class="tok-kw">script>