Portals on the server
A Portal renders nothing where it is written, so a dialog or sheet that is open at render time would be missing from the HTML.
A Portal renders nothing where it is written, so a dialog or sheet that is open at render time would be missing from
the HTML. Give the render a collector and emit it at the end of <body>:
src/entry-server.tsxtsx
import { createServerPortals, renderToStringAsync } from "@shadow-js/web/server";
const portals = createServerPortals();
const { html, data } = await renderToStringAsync(() => <App />, { portals });
const page =
`<!doctype html><html><body><div id="app">${html}</div>` +
`${portals.html()}</body></html>`;
- The content is server-rendered:
role="dialog"markup is in the document, and the client adopts it on hydration instead of creating a second copy, so there is no flash. The same holds forrenderToStringandrenderToStream(for a stream, readportals.html()after the stream has ended). - Each portal is one block, in render order. Nested portals are blocks of their own, and a render produces at most 256 of them.
@shadow-js/duskpasses a collector and emits the blocks at the end of<body>in every page mode, so a Dusk page needs no code for this.- A portal whose
mountis an element other thanbodyis placed at the end ofbodyby the server (the server does not know your element) and moved into the mount during hydration. Until then it shows at the end ofbody. Keep such a dialog closed at render time if that matters, or mount intobody. - Portals with
useShadoware not server-rendered; the browser creates them.
The contract, with the exact block format and the hydration rules, is in docs/contracts/ssr.md ("Portals").