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. 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 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.
Prerequisites
To continue in this article, it is important to have the following:
- Node.js runtime 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. Run the following command to install Strapi CMS on your local computer:
npx create-strapi-app@latest blog-app
Ensure to execute this command based on the following installation type:
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
.
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.
Then click the + Create new collection type button to set up the collection. Enter blog
as the content type name:
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.
Finally, create some test content for the above collection and its fields. Go ahead and click the + Create new entry button:
Fill in all the fields, and then make sure you publish the added content:
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.
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.
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:
npx create-remix@latest remix-blog-app
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:
cd remix-blog-app
You can test if the scaffolded Remix application works correctly by running:
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
:
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
:
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
:
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:
/* 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:
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:
// 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.
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:
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:
export const links = () => [
{ rel: "stylesheet", href: styles },
];
- Configure the loader for fetching the posts from Strapi:
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:
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:
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:
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 insideapp/routes
directory. - Create a
$postId.jsx
file in theposts
directory. - In the
$postId.jsx
file, import the necessary modules:
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:
export const links = () => [
{ rel: "stylesheet", href: styles },
];
- Configure a loader function to get each post:
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:
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:
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 for the Remix Frontend part.