Server rendering
Optional, and in its own package, so it costs nothing unless you want it.
@shineui/core is the same package whether or not you server-render. Hydration lives in @shineui/ssr, so a page that does not need it ships none of it.
You need both. @shineui/ssr renders the markup on the server. @shineui/ssr/components is the same elements compiled with hydration, so the browser takes that markup over instead of rebuilding it. Import the ordinary @shineui/core/components on a server-rendered page and it rebuilds, with the flash you were avoiding.
Registration is one call per element. Each file under @shineui/ssr/components/ exports a defineCustomElement for its own element, which keeps a server-rendered page from shipping all 146. Write the path with no .js on the end. To register every element at once, read the bulk module.
This package has no defineCustomElements(). That belongs to @shineui/core/loader, for a page that is not server-rendered.
- Plain HTML and Vite. The path above.
- React. The components render nothing on the server and while the page hydrates, and the elements draw in the render after, so a Next.js or Remix page hydrates with no mismatch. React cannot take over markup these elements drew: they render into their own light DOM, among the children React owns, and React reports error 418 and rebuilds the whole tree. A React page server-renders its own markup. Frameworks has the detail.
- Vue. As in React: the components render nothing on the server and while the page hydrates, and the elements draw with the content of their slots once the page has mounted, so a Nuxt page with server rendering on hydrates with no mismatch. Bundlers has the Nuxt setup.
- Angular. Keep hydration on and give each of your components that holds ShineUI elements
ngSkipHydration. Angular renders those afresh on the client and hydrates the rest of the page, incremental hydration included. Frameworks has the detail.
Server markup is attributes and children, and an array, an object or a function cannot be an attribute. Hand those to the renderer in beforeHydrate, which runs on the render's own document before the elements start: set the property on the element there, and the element renders with it. A table with its rows, a select with its options, a menu, a side nav and a chart all come back complete this way.
Set the same properties on the client before you register the elements, so the first client render matches the markup it takes over. A render callback, such as a column's render, runs on the server as well, where there is no global document: return a string, or build nodes with the document beforeHydrate was given. A callback that throws costs only its own cell, row or card, which falls back to what the element draws without one, and the error is logged once.
Children added after hydration
On a hydrated page, add a child to an element with the element's appendChild: the child lands at the end of its slot, inside the element's frame. To put one between two others, call before() or after() on a child that is already there, and take one out with its own remove(). The element's insertBefore and removeChild throw NotFoundError for a child in a slot on such a page, and its append and prepend leave the child outside the frame. A page that @shineui/core draws takes all of them. The hydrating build turns on one of Stencil's slot patches rather than all of them, because the others break on the nodes hydration takes over.
sui-code-block and sui-scheduler-builder load Shiki, sui-rich-text-editor and sui-edit-box load ProseMirror, and sui-qr-code loads the QR encoder, each only when its feature runs. Install them on the server only if you render those elements there. @shineui/ssr lists them as optional peer dependencies for that reason.
Content security policy
The renderer writes each element's styles into the head of the page. Under a style-src with no 'unsafe-inline', those style elements need the request's nonce. Generate a fresh nonce for every request, render the whole document with the nonce in a <meta name="csp-nonce"> in its head, and send the same nonce in the header. The renderer stamps it on every style element it writes, and in the browser the elements and applyTheme read the same tag. A fragment has no head, so its styles carry no nonce.
Security has the rest of the policy.
Language on the server
Give each render its language in its options. language sets <html lang> (a full document's own lang does the same), and locales hands that render its language packs, keyed by tag: the objects registerLocale takes in a browser. Built-in words such as Close or Next page, and numbers and dates, then come back in that language in the first response. A tag such as fr-CA falls back to the fr pack.
Each render runs in a window of its own, and the packs go into that window, so requests in different languages render at the same time without reaching each other. It is also why registerLocale and setLocale in your server code reach no render: on the server, the options are the way in. In the browser, register the same pack, and the elements keep speaking it after they hydrate. The locale prop on an element still wins over the page's language, on the server as in the browser.