Simply copy and paste the following command line in your terminal to create your first Strapi project.
npx create-strapi-app
my-project
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.
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.
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:
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.
To continue in this article, it is important to have the following:
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.
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:
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.
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:
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.
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:
1
2
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
.
Components set the application user interface. Remix uses components to set up different sections of a web application. Here will create the following components:
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
:1
2
3
4
5
6
7
8
9
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
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
:1
2
3
4
5
6
7
8
9
10
11
12
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/* 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:
1
2
3
4
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.
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:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 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.1
export default "http://localhost:1337";
To access the posts, we'll create Remix routes. In this case, we will have two major routes:
Let's create these routes.
Navigate to the app/routes/index.jsx
file and fetch all the blog posts as follows:
1
2
3
4
import { useLoaderData } from '@remix-run/react';
import { checkEnvVars, checkStatus } from '../utils/errorHandling';
import {Layout, BlogCard} from '../components';
import styles from '../components/style.css';
1
2
3
export const links = () => [
{ rel: "stylesheet", href: styles },
];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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:
At this point, Remix can serve the blog posts. However, we can't access the content on every single post. To do so:
posts
directory inside app/routes
directory.$postId.jsx
file in the posts
directory.$postId.jsx
file, import the necessary modules:1
2
3
4
5
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';
1
2
3
export const links = () => [
{ rel: "stylesheet", href: styles },
];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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:
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.
Joseph is fluent in Android Mobile Application Development and has a lot of passion for Fullstack web development. He loves to write code and document them in blogs to share ideas.