Remix > Setting up a preview mode

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 Remix 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.

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

First, we need to create a couple of API routes to enable/disable Preview Mode. We're going to use Remix's built-in session management 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.js:

1
import { createCookieSessionStorage } from "remix";
2
3
const { getSession, commitSession, destroySession } =
4
createCookieSessionStorage({
5
cookie: {
6
name: "__session",
7
maxAge: 604_800,
8
path: '/',
9
}
10
});
11
12
export { getSession, commitSession, destroySession };

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

1
import { redirect } from 'remix';
2
import { getSession, commitSession } from '~/sessions';
3
4
export const action = async ({ request }) => {
5
const session = await getSession(request.headers.get('Cookie'));
6
7
session.set('preview', 'yes');
8
9
return redirect('/', {
10
headers: {
11
'Set-Cookie': await commitSession(session),
12
},
13
});
14
};

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

1
import { redirect } from 'remix';
2
import { getSession, commitSession } from '~/sessions';
3
4
export const action = async ({ request }) => {
5
const session = await getSession(request.headers.get('Cookie'));
6
7
session.unset('preview');
8
9
return redirect('/', {
10
headers: {
11
'Set-Cookie': await commitSession(session),
12
},
13
});
14
};

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

1
import { getSession } from '~/sessions';
2
3
export const loader = async ({ request }) => {
4
const session = await getSession(request.headers.get('Cookie'));
5
return { previewEnabled: session.has('preview') };
6
};
7
8
export default function App() {
9
const { previewEnabled } = useLoaderData();
10
11
return (
12
<html lang="en">
13
<head>
14
<meta charSet="utf-8" />
15
<meta name="viewport" content="width=device-width,initial-scale=1" />
16
<Meta />
17
<Links />
18
</head>
19
<body>
20
{previewEnabled ? (
21
<Form method="post" action="/preview/stop">
22
<button>Exit preview mode</button>
23
</Form>
24
) : (
25
<Form method="post" action="/preview/start">
26
<button>Enter preview mode</button>
27
</Form>
28
)}
29
<Outlet />
30
<ScrollRestoration />
31
<Scripts />
32
{process.env.NODE_ENV === 'development' && <LiveReload />}
33
</body>
34
</html>
35
);
36
}

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 instead of the one that's currently set as published:

1
import { useLoaderData } from "remix";
2
import { load } from "~/lib/datocms";
3
4
const HOMEPAGE_QUERY = `query HomePage($limit: IntType) {
5
posts: allBlogPosts(first: $limit) {
6
title
7
}
8
}`;
9
10
export async function loader({ request }) => {
11
const session = await getSession(request.headers.get('Cookie'));
12
13
return load(HOMEPAGE_QUERY, {
14
variables: { limit: 10 }
15
includeDrafts: session.has('preview'),
16
});
17
};
18
19
export default function Home() {
20
const { posts } = useLoaderData();
21
22
return <div>{JSON.stringify(posts, null, 2)}</div>;
23
}