---
title: "Setup — React"
description: "Install the headless packages for your framework, create the engine once, and use them with server-side rendering."
framework: "React"
source: "https://www.cloudpdf.com/docs/headless/react/setup"
---

# Setup

A headless viewer needs two packages: the one for your framework, and an engine. This page
installs them, creates the engine for your whole app, and covers server-side rendering, bundlers
and fonts.

## Install

```sh
pnpm add @embedpdf/react@next @cloudpdf/engine@next
```

The framework package holds every plugin, one import path each (`@embedpdf/react/search`,
`@embedpdf/react/annotation`). A plugin you don't import isn't in your bundle.

It works with React 18 and later.

## Create the engine once

Create one engine when your app starts, and share it with every viewer:

```ts
// pdf.ts
import { cloudEngine } from '@cloudpdf/engine';

export const engine = cloudEngine();
```

It connects to CloudPDF SaaS. Your own server names itself:
`cloudEngine({ baseUrl: 'https://pdf.example.com' })`. Creating the engine makes no request.

`<Viewer>` can also own the engine. Pass a function, and the viewer creates the engine when it
mounts and destroys it when it unmounts:

**`owned-engine-cloud.tsx`**

```tsx
import { Viewer } from '@embedpdf/react/runtime';
import { cloudEngine } from '@cloudpdf/engine';
import { plugins } from './pdf';

export function DocumentViewer() {
  return (
    <Viewer engine={() => cloudEngine({ baseUrl: 'https://pdf.example.com' })} plugins={plugins} />
  );
}
```

An engine you pass in yourself is never destroyed by the viewer, so one engine can serve every
viewer in your app.

## Register the plugins

Plugins are a list you create once, next to the engine:

**`plugins.ts`**

```typescript
import { stagePlugin } from '@embedpdf/react/stage';
import { renderPlugin } from '@embedpdf/react/render';
import { interactionPlugin } from '@embedpdf/react/interaction';
import { selectionPlugin } from '@embedpdf/react/selection';

export const plugins = [stagePlugin(), renderPlugin(), interactionPlugin(), selectionPlugin()];
```

The viewer reads `engine` and `plugins` once, when it mounts. Creating the list inside a component
would make a new list on every render; the viewer ignores it and warns you while you develop.

Order doesn't matter, and a plugin that needs another says so when the app starts: the annotation
plugin, for example, needs the interaction plugin.

## Server-side rendering

The headless packages render nothing on the server that needs the engine: the engine only starts in
the browser. So a module-level engine is safe everywhere.

In the Next.js App Router, mark the component that renders `<Viewer>` as a client component:

**`ssr.tsx`**

```tsx
'use client';

import { Viewer } from '@embedpdf/react/runtime';
import { engine, plugins } from './pdf';

export function DocumentViewer() {
  return <Viewer engine={engine} plugins={plugins}>{/* … */}</Viewer>;
}
```

## Tokens

Each document opens with a token your backend creates: it says who the user is and what they may
do. Pass a function, so a long session gets fresh tokens:

**`tokens.tsx`**

```tsx
import { Viewer } from '@embedpdf/react/runtime';
import { fetchDocumentToken } from './api';
import { engine, plugins } from './pdf';

export function DocumentViewer({ documentId }: { documentId: string }) {
  return (
    <Viewer
      engine={engine}
      plugins={plugins}
      initialDocuments={[{ source: { kind: 'token', token: () => fetchDocumentToken(documentId) } }]}
    />
  );
}
```

[Authentication](https://www.cloudpdf.com/docs/engine/authentication) covers the three ways to open a document and what a
token carries. Fonts, limits and storage are your server's configuration.

## Good to know

### One viewer, many documents

A viewer can hold several open documents at once, like tabs. See
[Several documents](documents/tabs).

### Changing `engine` or `plugins`

Both are read once. To use another engine or another set of plugins, mount a new `<Viewer>`, for
example by giving it a new `key`.

> The ready-made [Viewer](https://www.cloudpdf.com/docs/viewer/react) uses these same packages. If you start there, everything on
> these pages still applies when you move to headless.
