> Lettrove docs 1.x · https://docs.lettrove.com/docs/get-started/nextjs

# Next.js

The Lettrove editor in a Next.js app (App Router): one route handler mints tokens on your server,
one client component shows the editor. Four files in all, starting from a new app.

You need a project's two **test** keys from [lettrove.com](https://lettrove.com) → **Settings →
Embed** (step 1 of the [Quickstart](/docs/get-started/quickstart#1-create-a-project-and-copy-its-test-keys)).
Test keys also work on `localhost`, so nothing needs adding to the allowed sites while you build.

## 1. Create the app and install two packages

```bash
npx create-next-app@latest lettrove-next --yes --ts --app --no-src-dir --use-npm
cd lettrove-next
npm install @lettrove/react @lettrove/node
```

`@lettrove/react` is the editor component, for the browser. `@lettrove/node` mints tokens with your
secret key, on your server only.

## 2. Your keys

Create `.env.local` in the app's folder:

```bash title=".env.local"
LETTROVE_SECRET_KEY=lt_sk_test_paste_yours_here
NEXT_PUBLIC_LETTROVE_PUBLISHABLE_KEY=lt_pk_test_paste_yours_here
```

The secret key has no `NEXT_PUBLIC_` prefix, so Next.js keeps it on the server: it never reaches a
browser. The publishable key is meant for the page.

## 3. The token route

The editor opens with a short-lived token. Create `app/lettrove-token/route.ts`:

```ts title="app/lettrove-token/route.ts"
import { Lettrove, LettroveApiError } from '@lettrove/node';

export async function POST(request: Request) {
  // In your product: your own id for the signed-in person, from your session. Never from the request body.
  const userId = 'demo-user';
  try {
    const lettrove = new Lettrove({ secretKey: process.env.LETTROVE_SECRET_KEY! });
    const { token } = await lettrove.tokens.create({
      user: { id: userId },
      origin: request.headers.get('origin')!, // the page the editor opens on
    });
    return Response.json({ token }, { headers: { 'Cache-Control': 'no-store' } });
  } catch (error) {
    console.error(error);
    const code = error instanceof LettroveApiError ? error.code : 'token_route_failed';
    return Response.json({ error: 'No token', code }, { status: 500 });
  }
}
```

## 4. The editor

`getToken` is a function, and Next.js does not pass functions from a server component to the
browser, so the editor lives in a small client component. Create `app/email-editor.tsx`:

```tsx title="app/email-editor.tsx"
'use client';

import { LettroveEditor } from '@lettrove/react';

async function getToken() {
  const response = await fetch('/lettrove-token', { method: 'POST' });
  return (await response.json()).token;
}

export function EmailEditor() {
  return (
    <LettroveEditor
      publishableKey={process.env.NEXT_PUBLIC_LETTROVE_PUBLISHABLE_KEY!}
      getToken={getToken}
      onDesignSaved={({ designId }) => console.log('saved', designId)}
      style={{ height: '100vh' }}
    />
  );
}
```

Then replace `app/page.tsx` with:

```tsx title="app/page.tsx"
import { EmailEditor } from './email-editor';

export default function Home() {
  return <EmailEditor />;
}
```

## 5. Run it

```bash
npm run dev
```

Open [http://localhost:3000](http://localhost:3000). The editor appears; add a heading, and the
browser's console prints `saved` and the design's id. Keep that id to open the same design again:
pass it as `designId`.

On the server, `npm run build` then `npm start` work as they are: the page renders an empty box on
the server, and the editor opens in the browser.

## Next

- [Getting designs out](/docs/editor/exports): `ref.current.exportHtml()` gives the email as HTML to send.
- [Theming and white label](/docs/editor/theming): your colours, light and dark, your name and logo.
- [Test and live keys](/docs/going-live/test-and-live): add your site before you switch to live keys.
- If the box shows a code instead of the editor: [Troubleshooting](/docs/going-live/troubleshooting).
