Skip to main content

ReactDOM Overview

ReactDOM is the entry point for React applications in web browsers. It provides methods for rendering React components to the DOM, managing root instances, and server-side rendering.

Package Structure

ReactDOM is split into several entry points based on usage:

Client APIs

import { createRoot, hydrateRoot } from 'react-dom/client';
Modern client rendering APIs (React 18+):
  • createRoot() - Create a root for client-side rendering
  • hydrateRoot() - Hydrate server-rendered content

Shared APIs

import { flushSync, createPortal } from 'react-dom';
APIs available in both client and server contexts:
  • flushSync() - Force synchronous updates
  • createPortal() - Render children into a different DOM node
  • prefetchDNS(), preconnect(), preload(), preloadModule() - Resource preloading
  • preinit(), preinitModule() - Resource initialization
  • useFormState(), useFormStatus() - Form handling hooks
  • requestFormReset() - Programmatically reset forms

Server APIs

import { renderToString, renderToStaticMarkup } from 'react-dom/server';
import { renderToPipeableStream } from 'react-dom/server';
Server-side rendering APIs:
  • renderToString() - Legacy synchronous server rendering
  • renderToStaticMarkup() - Render to static HTML without React attributes
  • renderToPipeableStream() - Modern streaming server rendering (Node.js)
  • renderToReadableStream() - Modern streaming server rendering (Web Streams)

Client vs Server Rendering

Client-Side Rendering (CSR)

Render React components directly in the browser:
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'));
root.render(<App />);
Pros:
  • Simple setup
  • Dynamic interactivity from the start
  • No server required after initial load
Cons:
  • Slower initial page load
  • Poor SEO without additional tooling
  • Blank page while JavaScript loads

Server-Side Rendering (SSR)

Render React components to HTML on the server, then hydrate on the client: Server:
import { renderToPipeableStream } from 'react-dom/server';

const { pipe } = renderToPipeableStream(<App />, {
  onShellReady() {
    response.setHeader('content-type', 'text/html');
    pipe(response);
  }
});
Client:
import { hydrateRoot } from 'react-dom/client';

hydrateRoot(document.getElementById('root'), <App />);
Pros:
  • Faster perceived load time
  • Better SEO
  • Content visible before JavaScript loads
Cons:
  • More complex setup
  • Requires server infrastructure
  • Hydration mismatches can cause issues

Static Site Generation (SSG)

Pre-render pages at build time:
import { renderToStaticMarkup } from 'react-dom/server';

const html = renderToStaticMarkup(<App />);
// Write to file system
Pros:
  • Fastest possible load times
  • Can be served from CDN
  • No server required
Cons:
  • Must rebuild for content changes
  • Not suitable for dynamic content
  • No React interactivity (unless you hydrate)

Rendering Modes

Concurrent Mode (Default)

React 18+ uses concurrent rendering by default with createRoot():
const root = createRoot(container);
root.render(<App />);
Features:
  • Interruptible rendering
  • Automatic batching
  • Transitions
  • Suspense for data fetching

Legacy Mode (React 17 and earlier)

Legacy mode is deprecated. Migrate to createRoot() for new applications.
// React 17 and earlier
import ReactDOM from 'react-dom';
ReactDOM.render(<App />, container);

Version Information

All ReactDOM packages export a version string:
import { version } from 'react-dom';
import { version as clientVersion } from 'react-dom/client';
import { version as serverVersion } from 'react-dom/server';

console.log(version); // "19.3.0"

Browser Compatibility

ReactDOM requires the following browser features:
  • ES6 Features: Map, Set, Promise
  • Modern DOM APIs: Element, Document, DocumentFragment
  • Event System: addEventListener, removeEventListener

Polyfills

For older browsers, include polyfills before React:
<script src="https://unpkg.com/core-js-bundle@3/minified.js"></script>
<script src="your-react-app.js"></script>

Environment Detection

ReactDOM automatically detects the environment:
  • Browser: Full client-side features
  • Node.js: Server rendering capabilities
  • Web Workers: Limited functionality
  • Edge Runtime: Optimized for edge computing

API Reference

Explore detailed documentation for each API:

createRoot

Create a root for rendering React components

hydrateRoot

Hydrate server-rendered HTML with React

renderToString

Legacy server rendering to string

renderToStaticMarkup

Render to static HTML without React

findDOMNode

Find DOM node (deprecated)

flushSync

Force synchronous updates

Migration Guide

From React 17 to React 18+

Before (React 17):
import ReactDOM from 'react-dom';

ReactDOM.render(<App />, container);
ReactDOM.unmountComponentAtNode(container);
ReactDOM.hydrate(<App />, container);
After (React 18+):
import { createRoot, hydrateRoot } from 'react-dom/client';

// Client rendering
const root = createRoot(container);
root.render(<App />);
root.unmount();

// Hydration
hydrateRoot(container, <App />);

Server Rendering Migration

Before:
import { renderToString } from 'react-dom/server';

const html = renderToString(<App />);
After:
import { renderToPipeableStream } from 'react-dom/server';

const { pipe } = renderToPipeableStream(<App />, {
  onShellReady() {
    pipe(response);
  }
});

Best Practices

  1. Use createRoot for new apps - Enables concurrent features
  2. Prefer streaming SSR - Better performance than renderToString
  3. Handle hydration errors - Use onRecoverableError callback
  4. Avoid findDOMNode - Use refs instead
  5. Minimize flushSync usage - Hurts performance
  6. Clean up on unmount - Call root.unmount() when done