npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

preact-render-to-string

v6.6.6

Published

Render JSX to an HTML string, with support for Preact components.

Readme

preact-render-to-string

NPM Build status

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) <<


Render JSX/VDOM to HTML

import { render } from 'preact-render-to-string';
import { h } from 'preact';
/** @jsx h */

let vdom = <div class="foo">content</div>;

let html = render(vdom);
console.log(html);
// <div class="foo">content</div>

Render Preact Components to HTML

import { render } from 'preact-render-to-string';
import { h, Component } from 'preact';
/** @jsx h */

// Classical components work
class Fox extends Component {
	render({ name }) {
		return <span class="fox">{name}</span>;
	}
}

// ... and so do pure functional components:
const Box = ({ type, children }) => (
	<div class={`box box-${type}`}>{children}</div>
);

let html = render(
	<Box type="open">
		<Fox name="Finn" />
	</Box>
);

console.log(html);
// <div class="box box-open"><span class="fox">Finn</span></div>

Render JSX / Preact / Whatever via Express!

import express from 'express';
import { h } from 'preact';
import { render } from 'preact-render-to-string';
/** @jsx h */

// silly example component:
const Fox = ({ name }) => (
	<div class="fox">
		<h5>{name}</h5>
		<p>This page is all about {name}.</p>
	</div>
);

// basic HTTP server via express:
const app = express();
app.listen(8080);

// on each request, render and return a component:
app.get('/:fox', (req, res) => {
	let html = render(<Fox name={req.params.fox} />);
	// send it back wrapped up as an HTML5 document:
	res.send(`<!DOCTYPE html><html><body>${html}</body></html>`);
});

Error Boundaries

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-string
export default () => {
	return <h1>Home page</h1>;
};
import { Suspense, lazy } from 'preact/compat';

// Creation of the lazy component
const HomePage = lazy(() => import('./pages/home'));

const Main = () => {
	return (
		<Suspense fallback={<p>Loading</p>}>
			<HomePage />
		</Suspense>
	);
};
import { renderToStringAsync } from 'preact-render-to-string';
import { Main } from './main';

const main = async () => {
	// Rendering of lazy components
	const html = await renderToStringAsync(<Main />);

	console.log(html);
	// <h1>Home page</h1>
};

// Execution & error handling
main().catch((error) => {
	console.error(error);
});

Streaming

[!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.

renderToReadableStream — Web Streams

import { renderToReadableStream } from 'preact-render-to-string/stream';
import { Suspense, lazy } from 'preact/compat';

const Profile = lazy(() => import('./Profile'));

const App = () => (
	<html>
		<head><title>My App</title></head>
		<body>
			<Suspense fallback={<p>Loading profile…</p>}>
				<Profile />
			</Suspense>
		</body>
	</html>
);

// Works in any Web Streams environment (Deno, Bun, Cloudflare Workers, …)
export default {
	fetch() {
		const stream = renderToReadableStream(<App />);

		// stream.allReady resolves once all suspended content has been flushed
		return new Response(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.

const stream = renderToReadableStream(<App />);
await stream.allReady; // wait for full render

renderToPipeableStream — Node.js Streams

import { createServer } from 'node:http';
import { renderToPipeableStream } from 'preact-render-to-string/stream-node';
import { Suspense, lazy } from 'preact/compat';

const Profile = lazy(() => import('./Profile'));

const App = () => (
	<html>
		<head><title>My App</title></head>
		<body>
			<Suspense fallback={<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 timeout
	setTimeout(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.


License

MIT