@veams/partial-hydration
v1.1.0
Published
The hydration package contains the core functionality to make components interactive.
Readme
@veams/partial-hydration
Selective hydration infrastructure for static HTML and islands-style UI delivery.
This package provides a framework-agnostic client-side hydration engine and optional React helpers for server-rendered markup preparation.
Docs
Live docs:
https://veams.github.io/status-quo/packages/partial-hydration/overview
Install
npm install @veams/partial-hydrationIf you want to use the React bindings:
npm install @veams/partial-hydration react react-domPackage Exports
Root entrypoint:
createHydrationHydrationOptionsComponentOption
React entrypoint:
@veams/partial-hydration/reactwithHydrationwithHydrationProvideruseIsomorphicId
Overview
@veams/partial-hydration is built around a simple contract:
- Server rendering emits static HTML for an interactive island.
- Serialized props are stored in a nearby
<script type="application/hydration-data">. - The client scans the DOM, matches wrappers via
data-component, and activates them when their trigger fires.
The core stays framework-neutral. You decide how rendering happens in the render() callback.
Quick Start
Create one hydration instance on the client and register your interactive islands:
import { hydrateRoot } from 'react-dom/client';
import { createHydration } from '@veams/partial-hydration';
import { NewsletterForm } from './NewsletterForm';
type NewsletterProps = {
title: string;
};
const hydration = createHydration({
components: {
NewsletterForm: {
Component: NewsletterForm,
on: 'in-viewport',
render: (Component, props, element) => {
hydrateRoot(element, <Component {...props} />);
},
},
},
});
hydration.init(document);Available triggers:
initdom-readyfonts-readyin-viewport
For in-viewport, you can also pass:
config: {
rootMargin: '200px';
}React SSR Flow
Use withHydration() during server rendering to generate the wrapper and serialized props automatically:
import { withHydration, useIsomorphicId } from '@veams/partial-hydration/react';
type NewsletterProps = {
title: string;
};
function NewsletterForm({ title }: NewsletterProps) {
const id = useIsomorphicId();
return (
<section aria-labelledby={id}>
<h2 id={id}>{title}</h2>
<button type="button">Subscribe</button>
</section>
);
}
NewsletterForm.displayName = 'NewsletterForm';
export const HydratedNewsletterForm = withHydration(NewsletterForm, {
modifiers: 'island island-newsletter',
attributes: {
'data-testid': 'newsletter-island',
},
});withHydration() does three things:
- serializes props into a script tag
- adds a wrapper with
data-componentanddata-internal-id - injects
HydrationProvidersouseIsomorphicId()stays stable inside the hydrated subtree
Generated DOM Shape
The client-side loader expects this structure:
<script type="application/hydration-data" data-internal-ref="NewsletterForm-abc123">
{"title":"Weekly updates"}
</script>
<div
data-component="NewsletterForm"
data-internal-id="NewsletterForm-abc123"
class="island island-newsletter"
></div>If the script is moved away from the wrapper before hydration, the package can still reconnect both nodes through data-internal-ref and data-internal-id.
Lazy Loading
You can defer downloading component code until activation:
import { hydrateRoot } from 'react-dom/client';
import { createHydration } from '@veams/partial-hydration';
type ChartProps = {
title: string;
};
const hydration = createHydration({
components: {
HeavyChart: {
Component: () => import('./HeavyChart'),
on: 'in-viewport',
config: {
rootMargin: '300px',
},
render: async (loadComponent, props, element) => {
const module = await loadComponent();
hydrateRoot(element, <module.default {...props} />);
},
},
},
});
hydration.init(document);API Notes
createHydration(options)returns{ init(context), clearAllObservers() }.init(context)acceptsdocumentor a specificHTMLElement.- Components are marked with
data-initialized="true"after successful activation. - The browser dispatches a
hydration:component:renderedevent after each successful render.
Important Constraints
- React components wrapped with
withHydration()should have a stabledisplayName. - Props must be serializable to JSON.
- The root package is framework-agnostic. React-specific helpers only live under
@veams/partial-hydration/react.
Development
npm run build --workspace=@veams/partial-hydration
npm run test --workspace=@veams/partial-hydration