Building a Blog App with Remix and Strapi CMS
Tags: javascript, webdev, remix, strapi
Published: 2022-08-31 07:43:20

_This article details the steps to building a blog app with Remix and Strapi CMS._

**Author: Joseph Chege**

[Strapi is an open-source headless CMS based on Node.js.](https://strapi.io/) It lets you develop and manage any content of your application. This allows you to build backend API faster with efficiency and customizability advantages.

## What is Remix?
On the other side, [Remix](https://remix.run/) is a good choice to consume your generated API endpoints. It offers data loading on the server side. Remix is a server-side rendered framework. It allows you to work seamlessly with the server and the client. Remix closely compare to Next.js framework.

## Remix vs Next.js
Next.js is one of the popular React frameworks for server-side rendering. Traditionally, React apps are rendered on the client side. When the initial request is sent from the web app, the browser is sent a shell of a bare HTML page that has no pre-rendered content. The browser would finally fetch the JavaScript file responsible for rendering the actual HTML page.

With the introduction of Server-Side Rendering (SSR) frameworks such as Next.js and Remix, a page is fully rendered on-demand on the server. This approach fits dynamic content that changes frequently.

Some of the reasons Remix is a good choice include:
- It's faster than Next.js.
- Remix has better performance.
- Remix leverages the native browser features. This allows Remix to reuse most platform-level features of HTML and HTTP to handle complex mutations. On the other side, if a Next.js app goes offline, the page will not respond, making it hard to handle mutations.
- Remix uses native HTML and it can work without JavaScript. This makes it easier to handle server-based errors creating a good experience for the user.

Remix and Next.js are closely related, and the difference between them is minimal. Check this guide to get a bigger picture of [how is Remix different from Next.js](https://remix.run/blog/remix-vs-next).

## Prerequisites
To continue in this article, it is important to have the following:
- [Node.js runtime](https://nodejs.org/en/) installed on your computer,
- Basic knowledge of JavaScript, and
- Prior experience working with a React-based framework such as Remix.

## Setting up Strapi Locally
Let's dive in and create a Strapi CMS backend. This will serve the blog posts to the users. To set up Strapi CMS, create a project folder. Then, open a command that points to this directory, preferably using a text editor such as the [Visual Studio Code](https://code.visualstudio.com/download). Run the following command to install Strapi CMS on your local computer:

```bash
    npx create-strapi-app@latest blog-app
```

Ensure to execute this command based on the following installation type:

![installation-types](https://paper-attachments.dropbox.com/s_9156662E04D090B304C8939E3C1D1248453406A5302DBC279C0B7D7A67F37246_1659267066797_create-strapi.png)

This will create a `blog-app` folder that will host all the project files and dependencies that Strapi CMS will need to run locally. Once the installation is done, you will be redirected to the Strapi admin page on your default browser at `http://localhost:1337/admin/auth/register-admin`.

![strapi-admin-page](https://paper-attachments.dropbox.com/s_9156662E04D090B304C8939E3C1D1248453406A5302DBC279C0B7D7A67F37246_1659267043819_strapi.png)

Provide the login credentials to access the Strapi admin. If you haven't created an account yet, provide Sign up details. Finally, click the **Let's start** button to access the admin dashboard.

## Build Blogs Collection with Strapi CMS
Here, we are creating a blog app. Therefore, we need to model the blog app backend content with Strapi. The first step is to set up the content type. On the Strapi admin panel, click **Content-Type Builder**.

![step_one](https://paper-attachments.dropbox.com/s_9156662E04D090B304C8939E3C1D1248453406A5302DBC279C0B7D7A67F37246_1659267091607_step_one.png)

Then click the **+ Create new collection type** button to set up the collection. Enter `blog` as the content type name:

![content_type_setup_name](https://paper-attachments.dropbox.com/s_9156662E04D090B304C8939E3C1D1248453406A5302DBC279C0B7D7A67F37246_1659267102228_content_type_setup_name.png)

Click **Continue** and set up the following blog fields:

- ***Title*** as a short text - A brief heading for your blog post.
- ***Excerpt*** as long text - A short summary of your post that will be displayed on the web app.
- ***Hero***: select media, single - A descriptive image for your blog post.
- ***Content***: as rich text - represents the content of your blog post.
![content_type_fields](https://paper-attachments.dropbox.com/s_9156662E04D090B304C8939E3C1D1248453406A5302DBC279C0B7D7A67F37246_1659267153940_content_type_fields.png)

Finally, create some test content for the above collection and its fields. Go ahead and click the **+ Create new entry** button:

![step_two](https://paper-attachments.dropbox.com/s_9156662E04D090B304C8939E3C1D1248453406A5302DBC279C0B7D7A67F37246_1659267165087_step_two.png)

Fill in all the fields, and then make sure you publish the added content:

![fields_data](https://paper-attachments.dropbox.com/s_9156662E04D090B304C8939E3C1D1248453406A5302DBC279C0B7D7A67F37246_1659267184361_fields_data.png)

Make sure you create at least three different blog posts using the same procedure as described above.

## Adding Roles & Permissions
Strapi CMS runs a decoupled application structure. To access its content, you need to set up permissions and roles that define what data should be exposed to your user channels.

In this example, we need Remix to interact with the collection we have created. Thus, we need to set up access to this collection so that Remix can execute the right request and get the expected responses.

To access the created `blog` collection, generate an API token that Remix will use to communicate with Strapi. To create the token:


- Navigate to the **Setting** section of the Strapi Dashboard.
- Click the API Tokens section.
- Click the Create new API Token button to set a token to consume the API.
![step_3](https://paper-attachments.dropbox.com/s_9156662E04D090B304C8939E3C1D1248453406A5302DBC279C0B7D7A67F37246_1659267203090_step_3.png)


Fill in the name and description in the respective fields. Finally, select a ***Read-only*** token type and then click save.

In this example, Remix will only read the content of our collection. If you need to add operations such as writing to Strapi CMS, ensure you modify your token type accordingly.

![creating_api_token](https://paper-attachments.dropbox.com/s_9156662E04D090B304C8939E3C1D1248453406A5302DBC279C0B7D7A67F37246_1659267214660_creating_api_token.png)

The API token will be generated. Copy the created token as we will use it with Remix.

## Setting up the Remix Application
Let's now create a frontend application to consume the content we have created with Strapi. On your project directory, you created earlier, run the following command to set up Remix:
```bash
    npx create-remix@latest remix-blog-app
```

![remix-setup](https://paper-attachments.dropbox.com/s_9156662E04D090B304C8939E3C1D1248453406A5302DBC279C0B7D7A67F37246_1659267224467_remix.png)

The above command will create a `remix-blog-app` folder that will host all the project files and dependencies that Remix will need to run a local web app.

Once the installation is done, proceed to the newly created directory:

```bash
    cd remix-blog-app
```
You can test if the scaffolded Remix application works correctly by running:
```bash
    npm run dev
```

Open `http://localhost:3000/` on a browser and you should be served with a hello world version of the Remix web app.

In the project Remix root directory (`remix-blog-app`), create a `.env` file and add in the following Remix configurations:

    STRAPI_API_TOKEN="your_access_token"
    STRAPI_URL_BASE="http://your_local_ip_address:1337"

Paste in your access token from the Strapi dashboard to replace the `your_access_token`.

## Setting up the Remix Components
Components set the application user interface. Remix uses components to set up different sections of a web application. Here will create the following components:
- A Navigation Bar: A basic web application navigation bar
- Blogpost Cards: Remix cards for displaying posts and their properties
- Layout: To set the overall layout of the web page

To set up these components, create a `components` folder in the `app` directory of the Remix project. Inside the `components` folder, create the following files and the respective code blocks to create the components:

#### `Navbar.jsx`:
```js
    export default function Navbar() {
        return (
            <nav className="navbar">
                <div className="nav-wrapper">
                    <a href="/" className="brand-logo">Blog App</a>
                </div>
            </nav>
        )
    }
```
Here, we are creating a `Navbar` that displays the application brand logo as a `Blog App` text. A click on this brand logo will redirect the user to the application home page.


#### `BlogCard.jsx`:
```js
    import { Link } from '@remix-run/react';
    import url from '../utils/url';
    
    export default function BlogCard({ blog }) {
        let data = blog.attributes;
        return (
            <div className="card">
                <div className="card-content">
                    <div className="card-img">
                        <img src={`${url}${data.hero.data.attributes.url}`} alt={data.hero.data.attributes.alternativeText} />
                    </div>
                    <div className="card-details">
    
                        <Link to={`/posts/${blog.id}`} className="card-title">
                            {data.title}
                        </Link>
    
                        <p className="card-excerpt">{data.excerpt}</p>
                    </div>
                </div>
            </div>
        )
    }
```
This `BlogCard` will display and arrange blogs in your application. Here, each post card will display the post title, hero image, and the post excerpt.

#### `Layout.jsx`:
```js
    import Navbar from './Navbar';
    
    export default function Layout({ children }) {
        return (
            <div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>
                <Navbar />
                <div className="container">
                    {children}
                </div>
            </div>
        );
    }
```

The `Layout` component runs a container that wraps the Navbar component to the main application.

#### `style.css`
To style the above components, add the following CSS code to a `style.css` file:
```css
    /* Navbar */
    .navbar {
      box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05);
      margin-bottom: 0;
      padding: 12px;
      position: relative;
    }
    
    .navbar .nav-wrapper a {
      text-decoration: none;
      font-size: 20px;
    }
    
    /* Layout */
    .container {
      width: 50%;
      margin: 0px auto;
    }
    
    /* Blog card */
    .card {
      width: 100%;
      padding: 10px;
      margin: 10px 0px;
      border-radius: 5px;
      box-shadow: 0 1px 3px 0 #d4d4d5, 0 0 0 1px #d4d4d5;
    }
    
    .card-content {
      width: 100%;
      display: flex;
      justify-content: space-between;
    }
    
    .card-content .card-img {
      width: 50%;
      height: 100%;
    }
    
    .card-img img {
      width: 90%;
      height: 100%;
      border-radius: 5px;
    }
    
    .card-details .card-title {
      text-decoration: none;
    }
```

#### `index.jsx`:
To execute the above components, create an `index.jsx` file and export it, as shown in the following code block:
```js
    import BlogCard from "./BlogCard";
    import Layout from "./Layout";
    import Navbar from "./Navbar";
    export { BlogCard, Layout, Navbar };
```
Exporting these components makes it easier for other application modules to access and use them according.

#### Setting up Remix Utilities
Create a `utils` folder inside the Remix `app` directory. Inside the `utils` folder, create the following files:

- **`errorHandling.js`**: For handling exemptions when connecting to the Strapi backend API:
```js
    // Custom error class for errors from Strapi API
    class APIResponseError extends Error {
        constructor(response) {
            super(`API Error Response: ${response.status} ${response.statusText}`);
        }
    }
    
    export const checkStatus = (response) => {
        if (response.ok) {
            // response.status >= 200 && response.status < 300
            return response;
        } else {
            throw new APIResponseError(response);
        }
    }
    
    class MissingEnvironmentVariable extends Error {
        constructor(name) {
            super(`Missing Environment Variable: The ${name} environment variable must be defined`);
        }
    }
    
    export const checkEnvVars = () => {
        const envVars = [
            'STRAPI_URL_BASE',
            'STRAPI_API_TOKEN'
        ];
    
        for (const envVar of envVars) {
            if (!process.env[envVar]) {
                throw new MissingEnvironmentVariable(envVar)
            }
        }
    }
```

- **`url.js`**: Host Strapi localhost URL.
```js
    export default "http://localhost:1337";
```

## Setting up the Routes
To access the posts, we'll create Remix routes. In this case, we will have two major routes:

- Route one to fetch all posts.
- Route two, which is a child route for every post, to help us access a single post.

Let's create these routes.

### Fetching all Posts
Navigate to the `app/routes/index.jsx` file and fetch all the blog posts as follows:

- Import all the necessary modules:
```js
    import { useLoaderData } from '@remix-run/react';
    import { checkEnvVars, checkStatus } from '../utils/errorHandling';
    import {Layout, BlogCard} from '../components';
    import styles from '../components/style.css';
```

- Configure the styles for the BlogCard component:
```js
export const links = () => [
        { rel: "stylesheet", href: styles },
    ];
```

- Configure the loader for fetching the posts from Strapi:
```js
    export async function loader() {
        checkEnvVars(); // check environmental variables
        const response = await fetch(`${process.env.STRAPI_URL_BASE}/api/blogs?populate=hero`, {
            method: "GET",
            headers: {
                "Authorization": `Bearer ${process.env.STRAPI_API_TOKEN}`,
                "Content-Type": "application/json"
            }
        }); // get the blogs
    
        checkStatus(response); // check the status
    
        const data = await response.json(); // get the json response
    
        if (data.error) { // error check
            throw new Response("Error loading data from strapi", { status: 500 });
        }
    
        return data.data; // return the data
    }
```

- Edit the render function to get the posts and render them on the BlogCard component:
```js
export default function Index() {
        const blogs = useLoaderData();
        return (
            <Layout>
                {
                    blogs.length > 0 ? (
                        blogs.map(blog => (
                            <BlogCard key={blog.id} blog={blog} />
                        ))
                    ) : (
                        <p>No blog posts found!</p>
                    )
                }
            </Layout>
        );
    }
```
This will be enough to display the blog posts on Remix. To test if it works as expected, ensure that the Remix development server is up and running using the following command:
```bash
    npm run dev
```

This will serve your application on `http://localhost:3000`. Use this link and open the application on a browser. Depending on the posts added, your page should be similar to:

![posts_page](https://paper-attachments.dropbox.com/s_9156662E04D090B304C8939E3C1D1248453406A5302DBC279C0B7D7A67F37246_1659267257324_posts_page.png)

### Fetching a Single Post
At this point, Remix can serve the blog posts. However, we can't access the content on every single post. To do so:

- Create a `posts` directory inside `app/routes` directory.
- Create a `$postId.jsx` file in the `posts` directory.
- In the `$postId.jsx` file, import the necessary modules:

```js
    import { useLoaderData } from '@remix-run/react';
    import { checkEnvVars, checkStatus } from '../../utils/errorHandling';
    import url from '../../utils/url';
    import { Layout } from '../../components';
    import styles from '../../components/style.css';
```

- Configure the styles:
```js
    export const links = () => [
        { rel: "stylesheet", href: styles },
    ];
```

- Configure a loader function to get each post:
```js
    export async function loader({ params }) {
        const { postId } = params; // get the post id
        checkEnvVars(); // check the environmental variables
        const response = await fetch(`${process.env.STRAPI_URL_BASE}/api/blogs/${postId}?populate=hero`, {
            method: "GET",
            headers: {
                "Authorization": `Bearer ${process.env.STRAPI_API_TOKEN}`,
                "Content-Type": "application/json"
            }
        }); // send a request to strapi backend to get the post
    
        checkStatus(response); // check the response status
    
        const data = await response.json(); // get the json data
    
        if (data.error) {// check if we have an error
            throw new Response("Error loading data from strapi", { status: 500 });
        }
    
        return data.data; // return the data
    }
```

- Configure a render function, to get the data fetched and display it:
```js
    export default function Post() {
        const blog = useLoaderData();
        const blogData = blog.attributes;
        return (
            <Layout>
                <div className="blog-post">
                    <div className="blog-post-hero">
                        <img src={`${url}${blogData.hero.data.attributes.url}`} alt={`${blogData.hero.data.attributes.alternativeText}`} />
                    </div>
                    <div className="blog-post-title">
                        <h1>{blogData.title}</h1>
                    </div>
                    <div className="blog-post-content">
                        <div dangerouslySetInnerHTML={{ __html: blogData.content }} />
                    </div>
                </div>
            </Layout>
        )
    }
```

Ensuring that the development server is up and running, click on any post on the home page. You will be directed to a page with the content of each specific blog as shown below:

![post_page](https://paper-attachments.dropbox.com/s_9156662E04D090B304C8939E3C1D1248453406A5302DBC279C0B7D7A67F37246_1659267240152_post_page.png)

## Conclusion
Strapi CMS is used to develop and manage the content of any application. It helps you scaffold any API faster and consume the content via Restful APIs and GraphQL. In this guide, we have created a Strapi Restful API and then consumed it to build a minimalistic Remix blog application.

You can get the source code used on this project on the [GitHub repository](https://github.com/kimkimani/strapi-remix-blog-app) for the Remix Frontend part.