Render JSX and Preact components to an HTML string.
Works in Node & the browser, making it useful for universal/isomorphic rendering.
>> Cute Fox-Related Demo(@ CodePen) <<
import{render}from'preact-render-to-string';import{h}from'preact';/** @jsx h */letvdom=<divclass="foo">content</div>;lethtml=render(vdom);console.log(html);// <div class="foo">content</div>import{render}from'preact-render-to-string';import{h,Component}from'preact';/** @jsx h */// Classical components workclassFoxextendsComponent{render({ name }){return<spanclass="fox">{name}</span>;}}// ... and so do pure functional components:constBox=({ type, children })=>(<divclass={`box box-${type}`}>{children}</div>);lethtml=render(<Boxtype="open"><Foxname="Finn"/></Box>);console.log(html);// <div class="box box-open"><span class="fox">Finn</span></div>importexpressfrom'express';import{h}from'preact';import{render}from'preact-render-to-string';/** @jsx h */// silly example component:constFox=({ name })=>(<divclass="fox"><h5>{name}</h5><p>This page is all about {name}.</p></div>);// basic HTTP server via express:constapp=express();app.listen(8080);// on each request, render and return a component:app.get('/:fox',(req,res)=>{lethtml=render(<Foxname={req.params.fox}/>);// send it back wrapped up as an HTML5 document:res.send(`<!DOCTYPE html><html><body>${html}</body></html>`);});Rendering errors can be caught by Preact via getDerivedStateFromErrors or componentDidCatch. To enable that feature in preact-render-to-string set errorBoundaries = true
import{options}from'preact';// Enable error boundaries in `preact-render-to-string`options.errorBoundaries=true;Suspense & lazy components with preact/compat
npm install preact preact-render-to-stringexportdefault()=>{return<h1>Home page</h1>;};import{Suspense,lazy}from'preact/compat';// Creation of the lazy componentconstHomePage=lazy(()=>import('./pages/home'));constMain=()=>{return(<Suspensefallback={<p>Loading</p>}><HomePage/></Suspense>);};import{renderToStringAsync}from'preact-render-to-string';import{Main}from'./main';constmain=async()=>{// Rendering of lazy componentsconsthtml=awaitrenderToStringAsync(<Main/>);console.log(html);// <h1>Home page</h1>};// Execution & error handlingmain().catch((error)=>{console.error(error);});Note
This is an early version of our streaming implementation.
Preact supports streaming HTML to the client incrementally, flushing <Suspense> fallbacks immediately and replacing them with the resolved content as data arrives. This reduces Time to First Byte and allows the browser to start parsing earlier.
import{renderToReadableStream}from'preact-render-to-string/stream';import{Suspense,lazy}from'preact/compat';constProfile=lazy(()=>import('./Profile'));constApp=()=>(<html><head><title>My App</title></head><body><Suspensefallback={<p>Loading profile…</p>}><Profile/></Suspense></body></html>);// Works in any Web Streams environment (Deno, Bun, Cloudflare Workers, …)exportdefault{fetch(){conststream=renderToReadableStream(<App/>);// stream.allReady resolves once all suspended content has been flushedreturnnewResponse(stream,{headers: {'Content-Type': 'text/html'}});}};The returned ReadableStream has an extra allReady: Promise<void> property that resolves once every suspended subtree has been written. Await it before sending the response if you need the complete document before anything is flushed (e.g. for static export). At which point you might be better off using renderToStringAsync though.
conststream=renderToReadableStream(<App/>);awaitstream.allReady;// wait for full renderimport{createServer}from'node:http';import{renderToPipeableStream}from'preact-render-to-string/stream-node';import{Suspense,lazy}from'preact/compat';constProfile=lazy(()=>import('./Profile'));constApp=()=>(<html><head><title>My App</title></head><body><Suspensefallback={<p>Loading profile…</p>}><Profile/></Suspense></body></html>);createServer((req,res)=>{res.setHeader('Content-Type','text/html');const{ pipe, abort }=renderToPipeableStream(<App/>,{onShellReady(){// Called once the synchronous shell is ready to stream.pipe(res);},onAllReady(){// Called once every suspended subtree has been flushed.},onError(error){console.error(error);res.statusCode=500;}});// Optional: abort the render after a timeoutsetTimeout(abort,10_000);}).listen(8080);| Option | Description |
|---|---|
onShellReady() | Called synchronously once the initial shell has been rendered and streaming is about to start. Pipe here for fastest TTFB. |
onAllReady() | Called after all <Suspense> boundaries have resolved and the stream is complete. |
onError(error) | Called for render errors inside suspended subtrees. |
Calling abort() stops the render and destroys the stream; any pending suspended subtrees are dropped.