Server-Side Rendering with Preact
If you’ve built apps with React and Vite, you already understand client-side rendering. The browser downloads a JavaScript bundle, executes it, and builds the DOM. It works, but users on slow connections stare at a blank screen while that bundle loads. Server-Side Rendering with Preact solves this by sending ready-to-display HTML from the server. Preact’s small runtime footprint makes that tradeoff especially appealing for performance-focused applications.
This article explains how Preact SSR works, what tooling is involved, and how hydration connects the server-rendered HTML to a live interactive app.
Key Takeaways
- Preact SSR renders your component tree to an HTML string on the server, giving users visible content before any JavaScript executes.
- The
preact-render-to-stringpackage provides synchronous, asynchronous, and streaming rendering APIs to fit different application needs. - Hydration attaches interactivity to server-rendered HTML using
hydraterather thanrender, preserving the existing DOM. - A Vite-based workflow with
@preact/preset-viteis the modern default for new Preact projects with SSR support. - Avoid hydration mismatches by keeping server and client output consistent — no timestamps, random IDs, or
windowaccess during the server pass.
What Is Preact SSR and Why Does It Matter?
With SSR, your component tree renders to an HTML string on the server before the browser receives anything. The user sees content immediately. JavaScript loads in the background and attaches interactivity afterward.
The practical benefits are real:
- Faster perceived load times — visible content arrives with the initial HTTP response.
- Better SEO — crawlers read fully-formed HTML without executing JavaScript.
- Resilience — pages remain readable even if client-side JS fails or is slow.
Preact is particularly well-suited for SSR because its small runtime adds minimal overhead.
The Core Tool: preact-render-to-string
Preact SSR depends on preact-render-to-string, a separate package that handles server-side rendering.
npm install preact-render-to-string
Synchronous Rendering
For components with no async dependencies, renderToString converts your component tree to HTML in one pass:
import { renderToString } from 'preact-render-to-string';
const App = () => <h1>Hello from the server</h1>;
const html = renderToString(<App />);
// → <h1>Hello from the server</h1>
Async Rendering
When components fetch data or use Suspense with lazy-loaded chunks, use renderToStringAsync instead. It waits for promises and async rendering work to complete before returning the final HTML string.
import { renderToStringAsync } from 'preact-render-to-string';
const html = await renderToStringAsync(<App />);
Streaming
For larger pages, streaming APIs let you send HTML to the browser in chunks as each section renders. renderToPipeableStream targets Node.js streams, while renderToReadableStream targets environments using the Web Streams API, including platforms like Cloudflare Workers, Deno, and Bun. Streaming improves Time to First Byte (TTFB) without waiting for the full render to complete.
Preact Hydration: Connecting HTML to Interactivity
Sending static HTML is only half the job. To make the page interactive, Preact needs to hydrate the existing DOM — attaching event listeners and state without re-rendering from scratch.
import { hydrate } from 'preact';
import { App } from './app.js';
hydrate(<App />, document.getElementById('root'));
Use hydrate instead of render when the DOM was already produced by the server. Using render would discard the server HTML and rebuild it, defeating the purpose.
Hydration mismatches happen when the server-rendered HTML doesn’t match what the client would render. Common causes include rendering timestamps, random IDs, or reading window during the server pass. Keep server and client component logic consistent to avoid them.
Discover how at OpenReplay.com.
Preact Vite SSR: The Modern Setup
For new projects, a Vite-based workflow is the practical default. Vite’s SSR guide documents the dual-build pattern: one build targeting the server entry and another for the client bundle. Preact integrates cleanly with @preact/preset-vite, which handles JSX, aliases, and Preact-specific setup.
For teams that want a more structured starting point, preact-iso provides lightweight routing and prerendering utilities designed specifically for Preact Vite projects.
What to Keep in Mind
Preact SSR shares conceptual ground with React SSR but isn’t identical in every implementation detail. Some React-specific SSR APIs don’t have direct equivalents, and the ecosystem around Preact is smaller — a fair tradeoff for the size and performance gains.
The pattern that works reliably: fetch your data before rendering, pass it as props, render to string on the server, hydrate on the client. Start there, then layer in streaming or more advanced rendering patterns once you have a reason to.
Conclusion
Preact SSR offers a lightweight, performance-focused path to server-rendered applications without the overhead of a larger framework. By combining preact-render-to-string for the server pass, hydrate for client-side activation, and Vite for the build pipeline, you get a setup that’s fast to ship and easy to reason about. Begin with the basic render-and-hydrate flow, keep your server and client output aligned, and reach for streaming only when your application’s scale justifies it.
FAQs
Choose Preact SSR when bundle size, cold-start performance, or edge deployment matters more than access to React's broader ecosystem. Preact's small runtime is ideal for content-heavy sites, marketing pages, and workers-based deployments. Stick with React SSR if you depend on React-specific libraries or need APIs like React Server Components that Preact doesn't replicate.
Ensure the server and client render the same output for the same props. Avoid non-deterministic values during the initial render, such as Date.now, Math.random, or browser-only globals like window and localStorage. If you need client-only content, render a stable placeholder on the server and update it inside an effect after hydration completes.
Many React libraries work through the preact/compat alias, which maps React imports to Preact equivalents. However, libraries that depend on React-specific SSR features, such as React Server Components or rendering internals, may not behave correctly. Test each dependency in your SSR pipeline before committing.
Probably not. Streaming pays off when pages are large, data-heavy, or have sections that resolve at different speeds. For a typical small site, renderToString or renderToStringAsync produces results quickly enough that streaming adds complexity without measurable benefit. Start with the simpler synchronous or async APIs and adopt streaming only when TTFB becomes a real bottleneck.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.