CHART/DOCS/SERVER & CLIENT

Server and client

The boundary is a directive on the file, and a marker at the call site.

The boundary

A // @runsOn server component may fetch data with await data(...) and render client components. Crossing the boundary requires an explicit marker at the JSX call site:

// @runsOn server
import { data } from '@marisjs/runtime';
import { Stats } from '../islands/Stats.tsx';

type Props = {};

export function Dashboard(props: Props) {
  const products = await data(async () => {
    const res = await fetch('https://api.example.com/products');
    return res.json();
  });

  return (
    <div>
      <h1>Dashboard</h1>
      <Stats items={products} client:hydrate />
    </div>
  );
}

Everything left of the marker is static HTML produced at build time. Everything right of it is a compiled island module, hydrated in place when the page loads.

What hydration means here

  • Only components marked client:hydrate ship JavaScript to the browser.
  • The page's compiled module imports exactly the islands it uses - nothing else.
  • Each island mounts onto its own element and manages its own signals.
  • A failed island module does not take down the rest of the page's islands.
Props cross the boundary: island props are serialized through SSR into the placeholder's data-props attribute - dynamic values from data() and expressions included. Every instance mounts with its own payload, and using the same island twice on one page is fine: one import, N mounts.
One-way street: server files may import client files, but a client file importing a server component is a hard build error (CLIENT_IMPORTS_SERVER). Server data flows into islands via props, not imports.