---
title: "Getting Started — React"
description: "Build your own PDF viewer UI with the EmbedPDF headless libraries."
framework: "React"
source: "https://www.cloudpdf.com/docs/headless/react/getting-started"
---

# Getting Started

The headless libraries give you the engine and the plugin system — you own
every pixel of the UI. An app depends on two packages: the adapter for your
framework, and an engine.

## Installation

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

The React adapter exposes every feature as one import line per vertical —
registration, components, and hooks travel together, and deleting the line
removes the feature from your bundle.

## Your first viewer

`cloudEngine({ baseUrl })` creates the engine — synchronously and cheaply: it
holds an HTTP client and nothing else, so a module-scope
`const engine = cloudEngine({ baseUrl })` is safe (even under SSR). Hand it to
`<Viewer>`; every open, render, and text call rides HTTPS to your CloudPDF
deployment. This is the whole app:

**`basic.tsx`**

```tsx
import { Viewer, DocumentGate } from '@embedpdf/react/runtime';
import type { OpenInput } from '@embedpdf/react/runtime';
import { Stage, stagePlugin } from '@embedpdf/react/stage';
import { RenderLayer, renderPlugin } from '@embedpdf/react/render';
import { cloudEngine } from '@cloudpdf/engine';

// The engine is created synchronously and costs nothing until first use, so
// a module-scope `const engine = …` is safe — even under SSR. Only opening a
// document does real work: the UI renders at t≈0.
const engine = cloudEngine({ baseUrl: 'https://engine.cloudpdf.com' });
const plugins = [stagePlugin(), renderPlugin()];

const ebook: OpenInput = { kind: 'share', shareToken: 'shr_WGj1goAtlNN_fQ5OswPrbJQM' };

export default function App() {
  return (
    <Viewer engine={engine} plugins={plugins} initialDocuments={[{ source: ebook }]}>
      <div style={{ height: 500 }}>
        {/* Document UI is defined over a document — gate it on having one. */}
        <DocumentGate fallback={<p>Loading…</p>}>
          <Stage style={{ height: '100%' }}>{() => <RenderLayer />}</Stage>
        </DocumentGate>
      </div>
    </Viewer>
  );
}
```

Prefer the viewer to own the engine's lifetime — created on mount, destroyed on
unmount? Pass a thunk instead: `engine={() => cloudEngine({ baseUrl })}`. See
the [Engine getting started](https://www.cloudpdf.com/docs/engine/getting-started) for tokens, public
shares, and the ownership model.
