Creating post previews in Strapi with Remix

Roger Stringer
July 21, 2022
3 min read

Being able to preview a new post is important, and when headless, it's even more important.

This little script sits at app/routes/blog/preview.tsx (You can move it anywhere you want of course) and we'll call it with some query parameters such as post for the post ID and secret which will be an env var called PREVIEW_SECRET:

// app/routes/blog/preview.tsx:
1import type { LoaderFunction } from "@remix-run/node";
2import { json } from "@remix-run/node";
3import { useLoaderData } from "@remix-run/react";
4import { marked } from "marked";
5import * as React from "react";
6
7type Post = {
8  title: string;
9  article: string;
10  createdAt: string;
11  updatedAt: string;
12  publishedAt: string;
13};
14
15type PostData = { id: string; attributes: Post }[];
16
17type PostResponse = {
18  data: PostData;
19  meta: {
20    pagination: {
21      page: number;
22      pageSize: number;
23      pageCount: number;
24      total: number;
25    };
26  };
27};
28
29export const loader: LoaderFunction = async ({request}) => {
30    const url = new URL(request.url);
31    const id = url.searchParams.get("post") || 1;
32    const secret = url.searchParams.get("secret") || 100;  
33
34    if( !id ) {
35        throw json({ errors: [{ 
36            id: "post is invalid" ,
37            secret: null,
38        }] }, 400);
39    }
40    if( secret !== process.env.PREVIEW_SECRET ){
41        throw json({ errors: [{ 
42            id: null,
43            secret: "secret is invalid",
44        }] }, 403);
45    }
46
47  // This is where Remix integrates with Strapi
48  const response = await fetch(`http://localhost:1337/api/posts/${id}?publicationState=preview`);
49
50  const postResponse = (await response.json()) as PostResponse;
51  const post = postResponse.data;
52  return json(
53    {
54      ...post,
55      attributes: {
56        ...post.attributes,
57        article: marked(post.attributes.article),
58      },
59    }
60  
61};
62
63const Posts: React.FC = () => {
64  const post = useLoaderData<PostData>();
65  const { title, article, createdAt } = post.attributes;
66  const date = new Date(createdAt).toLocaleString("en-US", {
67    weekday: "long",
68    year: "numeric",
69    month: "long",
70    day: "numeric",
71  });
72
73  return (
74    <article key={post.id}>
75        <h1>{title}</h1>
76        <time dateTime={createdAt}>{date}</time>
77        {/* Reminder that this can in fact be dangerous
78            https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml */}
79        <div dangerouslySetInnerHTML={{ __html: article }} />
80    </article>
81  );
82};
83export default Posts;
84

The big part of this page is that is uses publicationState=preview when calling Strapi, this means you can return posts that are not yet live in the Strapi database and lets you preview it. Obviously this makes the secret important but that's also up to you.

This is pretty light overall, since you're probably going to need to customize fields, etc for your own Strapi setup, as well as how it looks but I wanted to share this as a starting point.

I originally wrote this post on my Coded Geekery blog, but cross posting to share here as well.

Do you like my content?

Sponsor Me On Github