Skip to main content
Rendering is the process of converting your components into actual DOM elements that appear in the browser.

The render function

Preact’s render() function mounts a component tree into a DOM element (src/render.js:12):
export function render(vnode, parentDom) {
  // https://github.com/preactjs/preact/issues/3794
  if (parentDom == document) {
    parentDom = document.documentElement;
  }

  if (options._root) options._root(vnode, parentDom);

  let isHydrating = vnode && vnode._flags & MODE_HYDRATE;

  // To be able to support calling `render()` multiple times on the same
  // DOM node, we need to obtain a reference to the previous tree.
  let oldVNode = isHydrating ? NULL : parentDom._children;

  parentDom._children = createElement(Fragment, NULL, [vnode]);

  // List of effects that need to be called after diffing.
  let commitQueue = [],
    refQueue = [];

  diff(
    parentDom,
    parentDom._children,
    oldVNode || EMPTY_OBJ,
    EMPTY_OBJ,
    parentDom.namespaceURI,
    oldVNode
      ? NULL
      : parentDom.firstChild
        ? slice.call(parentDom.childNodes)
        : NULL,
    commitQueue,
    oldVNode ? oldVNode._dom : parentDom.firstChild,
    isHydrating,
    refQueue,
    parentDom.ownerDocument
  );

  // Flush all queued effects
  commitRoot(commitQueue, parentDom._children, refQueue);
}

Basic usage

Import render and mount your app:
import { render } from 'preact';

function App() {
  return <h1>Hello, World!</h1>;
}

render(<App />, document.body);
Rendering to document is automatically converted to document.documentElement to avoid issues.

Mounting to a container

It’s common to render into a dedicated container element:
<!DOCTYPE html>
<html>
  <body>
    <div id="app"></div>
    <script src="bundle.js"></script>
  </body>
</html>
import { render } from 'preact';
import App from './App';

render(<App />, document.getElementById('app'));

Initial render vs updates

Preact handles initial renders and subsequent updates differently:

Initial render

On first render, Preact:
  1. Creates a VNode tree from your components
  2. Generates real DOM nodes
  3. Inserts them into the parent container
  4. Calls lifecycle methods like componentDidMount

Subsequent renders

When you call render() again on the same container:
  1. Preact finds the previous VNode tree (stored in parentDom._children)
  2. Diffs the new tree against the old one
  3. Only updates the parts that changed
  4. Calls update lifecycle methods
Preact automatically detects if you’re rendering into a container that already has a Preact app and performs an efficient update.

Replacing content

When you render into a container, Preact replaces any existing content:
<div id="app">
  <p>Loading...</p>
</div>
The <p>Loading...</p> is completely replaced by the rendered content.

Hydration

Hydration is the process of attaching Preact to server-rendered HTML instead of replacing it (src/render.js:66):
export function hydrate(vnode, parentDom) {
  vnode._flags |= MODE_HYDRATE;
  render(vnode, parentDom);
}

When to use hydrate

Use hydrate() when:
  • You’re using server-side rendering (SSR)
  • The DOM already contains the rendered HTML
  • You want to attach event listeners and make it interactive

Hydration example

<!DOCTYPE html>
<html>
  <body>
    <div id="app">
      <h1>Hello, World!</h1>
      <p>This was rendered on the server</p>
    </div>
    <script src="bundle.js"></script>
  </body>
</html>
The component tree passed to hydrate() must match the server-rendered HTML exactly, or Preact will log warnings.

Rendering process

Here’s what happens when you call render():

Step-by-step breakdown

  1. Prepare the VNode - The component is wrapped in a Fragment
  2. Find previous tree - Check if this container was rendered before
  3. Diff - Compare new and old VNode trees
  4. Update DOM - Apply minimal changes to real DOM
  5. Commit - Make all DOM changes visible at once
  6. Effects - Run refs, lifecycle callbacks, etc.

Real-world example

Here’s a complete example from Preact’s demo (demo/index.jsx:197):
import { render, Component } from 'preact';
import { Router, Link } from 'preact-router';
import Home from './home';
import Todo from './todo';
import Context from './context';

class App extends Component {
  render({ url }) {
    return (
      <div class="app">
        <header>
          <nav>
            <Link href="/" activeClassName="active">Home</Link>
            <Link href="/todo" activeClassName="active">ToDo</Link>
            <Link href="/context" activeClassName="active">Context</Link>
          </nav>
        </header>
        <main>
          <Router url={url}>
            <Home path="/" />
            <Todo path="/todo" />
            <Context path="/context" />
          </Router>
        </main>
      </div>
    );
  }
}

render(<App />, document.body);
This example:
  • Imports render from Preact
  • Defines an App component
  • Renders it into document.body
  • Sets up client-side routing

Multiple render calls

You can call render() multiple times on the same container to update the UI:
import { render } from 'preact';

let count = 0;
const container = document.getElementById('app');

function update() {
  render(<h1>Count: {count}</h1>, container);
}

update(); // Initial render

setInterval(() => {
  count++;
  update(); // Updates existing render
}, 1000);
Each call to render() on the same container performs an efficient diff and only updates what changed.

Unmounting

To unmount a component tree, render null:
import { render } from 'preact';

const container = document.getElementById('app');

// Mount app
render(<App />, container);

// Later: unmount and clean up
render(null, container);
This:
  • Calls componentWillUnmount() on all components
  • Removes all DOM nodes
  • Cleans up event listeners and refs

Rendering to specific elements

You can render multiple independent Preact apps on the same page:
import { render } from 'preact';

// Render header
render(
  <Header />,
  document.getElementById('header')
);

// Render main app
render(
  <App />,
  document.getElementById('app')
);

// Render footer
render(
  <Footer />,
  document.getElementById('footer')
);
Each render is independent and maintains its own state.

Rendering fragments

You can render fragments directly:
import { render, Fragment } from 'preact';

render(
  <Fragment>
    <h1>Title</h1>
    <p>Content</p>
  </Fragment>,
  document.getElementById('app')
);
This renders both elements as siblings inside the container.

Performance considerations

Avoid rendering to document.body

Rendering directly to document.body can interfere with browser extensions and dev tools:
render(<App />, document.getElementById('app'));

Reuse containers

When updating, always render to the same container:
const container = document.getElementById('app');
render(<App count={1} />, container);
render(<App count={2} />, container); // Efficient update

Error boundaries

Wrap your render in a try-catch for error handling:
import { render } from 'preact';
import App from './App';
import ErrorFallback from './ErrorFallback';

try {
  render(<App />, document.getElementById('app'));
} catch (error) {
  console.error('Failed to render app:', error);
  render(
    <ErrorFallback error={error} />,
    document.getElementById('app')
  );
}
For better error handling, consider using error boundary components that catch errors in their child components.

Build docs developers (and LLMs) love