slim-react

hadars's lightweight React-compatible SSR renderer — no react-dom/server required.

What it is

slim-react is hadars's own server-side renderer, located in src/slim-react/. It replaces react-dom/server entirely on the server side. Your components and any libraries they import render through it automatically — no code changes required.

For SSR builds, rspack aliases react and react/jsx-runtime to slim-react, so third-party components render through it transparently.

How it works

1render(element)Walks the React element tree recursively, calling function components and resolving JSX.
2Suspense loopThrown Promises are caught, awaited, and the component is retried. Repeats until all data resolves.
3HTML stringReturns the fully resolved HTML string, compatible with React's hydrateRoot.

Supported React features

FeatureStatusNotes
React.memoWrapped components render normally.
React.forwardRefRefs are ignored on the server (no DOM), render proceeds.
React.lazyThe lazy promise is awaited before rendering.
Context.ProviderFull context propagation through the tree.
Context.ConsumerReads the nearest Provider value.
React.SuspenseThrown Promises are awaited and the component is retried.
async componentsAsync function components are awaited directly.
React.useId()Generates tree-position-based IDs matching React's client output.
useState / useEffectpartialuseState returns the initial value. useEffect is a no-op on the server.
useRef / useCallback / useMemopartialReturn the initial value or identity — no caching between renders.

useId() compatibility

slim-react implements React 19's tree-position-based useId algorithm. IDs are derived from the component's position in the render tree, so they are deterministic across the server render and client hydration.

When your components call React.useId(), slim-react intercepts the call via a dispatcher shim and routes it through the same algorithm, producing IDs that React's hydrateRoot will agree with — no hydration warnings.

// Works in any component during SSR — no special imports needed
const FormField = ({ label }) => {
    const id = React.useId(); // slim-react handles this during SSR
    return (
        <>
            <label htmlFor={id}>{label}</label>
            <input id={id} />
        </>
    );
};

Emotion / CSS-in-JS

Because slim-react renders through the same React component model, CSS-in-JS libraries like @emotion/styled and @emotion/react work out of the box — styles are inlined into the rendered HTML on the server, and the client hydrates without a flash of unstyled content.