GitHub

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.

Never set { 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
Start without 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>