# Setting up a preview mode

Most often than not, editors of a DatoCMS project will find very beneficial to have a preview of how the changes they are making to ie. an article will be rendered inside the final website.

With React Router you can easily add a "preview mode" to your production website. With that, requests coming from editors will add a special header — `X-Include-Drafts` — that [returns content that is not yet published](https://www.datocms.com/docs/content-delivery-api/api-endpoints.md#include-drafts).

### Step 1: Create resource routes to turn Preview mode on/off

First, we need to create a couple of [resource routes](https://reactrouter.com/how-to/resource-routes) to enable/disable Preview Mode. We're going to use React Router's [built-in session management](https://reactrouter.com/api/utils/createCookieSessionStorage) to store a cookie inside the browser of the visitor.

First step is to actually create the session manager. Create a new file under `app/sessions.ts`:

app/sessions.ts

```typescript
import { createCookieSessionStorage } from 'react-router';

const { getSession, commitSession, destroySession } = createCookieSessionStorage({
  cookie: {
    name: '__session',
    maxAge: 604_800,
    path: '/',
  },
});

export { getSession, commitSession, destroySession };
```

Now we can use it inside a new resource route under `app/routes/preview-start.ts`, that we can call to turn on the preview mode:

app/routes/preview-start.ts

```typescript
import { redirect } from 'react-router';
import { getSession, commitSession } from '~/sessions';
import type { Route } from './+types/preview-start';

export async function action({ request }: Route.ActionArgs) {
  const session = await getSession(request.headers.get('Cookie'));

  session.set('preview', 'yes');

  return redirect('/', {
    headers: {
      'Set-Cookie': await commitSession(session),
    },
  });
}
```

Similarly, we also need to create a route under `app/routes/preview-stop.ts`, to turn preview mode off:

app/routes/preview-stop.ts

```typescript
import { redirect } from 'react-router';
import { getSession, commitSession } from '~/sessions';
import type { Route } from './+types/preview-stop';

export async function action({ request }: Route.ActionArgs) {
  const session = await getSession(request.headers.get('Cookie'));

  session.unset('preview');

  return redirect('/', {
    headers: {
      'Set-Cookie': await commitSession(session),
    },
  });
}
```

Both routes only export an `action`, with no component to render, so we declare them in `app/routes.ts` as any other route:

app/routes.ts

```typescript
import { type RouteConfig, index, route } from '@react-router/dev/routes';

export default [
  index('routes/home.tsx'),
  route('preview/start', 'routes/preview-start.ts'),
  route('preview/stop', 'routes/preview-stop.ts'),
] satisfies RouteConfig;
```

We can now tweak the `app/root.tsx` file to add to every page a button to toggle the preview on and off:

app/root.tsx

```tsx
import {
  Form,
  Links,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
  useRouteLoaderData,
} from 'react-router';
import { getSession } from '~/sessions';
import type { Route } from './+types/root';

export async function loader({ request }: Route.LoaderArgs) {
  const session = await getSession(request.headers.get('Cookie'));
  return { previewEnabled: session.has('preview') };
}

export function Layout({ children }: { children: React.ReactNode }) {
  const data = useRouteLoaderData('root');

  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width,initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
        {data?.previewEnabled ? (
          <Form method="post" action="/preview/stop">
            <button>Exit preview mode</button>
          </Form>
        ) : (
          <Form method="post" action="/preview/start">
            <button>Enter preview mode</button>
          </Form>
        )}
        {children}
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  );
}

export default function App() {
  return <Outlet />;
}
```

### Step 2: Fetch non-published content with X-Include-Drafts

Now every `loader` can know from the session if the visitor is currently in preview mode by looking at the `request` object.

If that's the case, we can run the same query, but passing the `X-Include-Drafts` header, which [returns the records at their **latest version available**](https://www.datocms.com/docs/content-delivery-api/api-endpoints.md#include-drafts) instead of the one that's currently set as published:

app/routes/home.tsx

```tsx
import type { Route } from './+types/home';
import { load } from '~/lib/datocms';
import { getSession } from '~/sessions';

const HOMEPAGE_QUERY = `query HomePage($limit: IntType) {
  posts: allBlogPosts(first: $limit) {
    title
  }
}`;

export async function loader({ request }: Route.LoaderArgs) {
  const session = await getSession(request.headers.get('Cookie'));

  return load(HOMEPAGE_QUERY, {
    variables: { limit: 10 },
    includeDrafts: session.has('preview'),
  });
}

export default function Home({ loaderData }: Route.ComponentProps) {
  const { posts } = loaderData;

  return <div>{JSON.stringify(posts, null, 2)}</div>;
}
```

## Related content in "React Router"

- [React Router + DatoCMS Overview](https://www.datocms.com/docs/react-router.md)
- [Managing images](https://www.datocms.com/docs/react-router/managing-images.md)
- [Displaying videos](https://www.datocms.com/docs/react-router/displaying-videos.md)
- [Structured Text fields](https://www.datocms.com/docs/react-router/structured-text-fields.md)
- [Adding SEO to pages](https://www.datocms.com/docs/react-router/seo-management.md)
- [Setting up a preview mode](https://www.datocms.com/docs/react-router/setting-up-a-preview-mode.md)
- [Real-time updates](https://www.datocms.com/docs/react-router/real-time-updates.md)