Content management systems (CMS) are a useful and popular tool for creating web pages, blogs, and online stores. WordPress in particular powers about 42.5 percent of all websites, including CSS-Tricks. If you like the ease of CMS but are looking for a faster, more efficient solution, Strapi is perfect.
Strapi is a JavaScript-based headless CMS. In a headless CMS, the content is decoupled from the presentation. In other words, an API serves up your content to be consumed by a separate head, or frontend. This is different from traditional CMSes, which are sometimes referred to as “monolithic” because the content and presentation are combined.
So why would you want to decouple content from the presentation? There are many reasons. First, writers can focus on writing, and developers can focus on developing. With a traditional CMS, the developer is bound by the constraints of that particular CMS. For example, WordPress themes need to be constructed in a very specific manner using PHP. With a headless CMS, writers still have access to post content using a familiar portal, and developers are free to use whatever technology they want to create the frontend.
Second, your site will benefit from enhanced security. With a headless CMS, the content publishing platform cannot be accessed from the database, which translates to less possibility of DDoS attacks.
Finally, using a headless CMS allows you to manage content for multiple channels. Your content is being served up as pure data and is not bound to any particular UI structure. That means you have the flexibility to easily display it in different ways.
CSS-Tricks is a great resource for developers, but it’s built using WordPress. Using this tutorial, you’ll create a basic CSS-Tricks clone using Strapi as your CMS and Next.js for the frontend.
A content model represents the pieces of your site and how they fit together. If you’ve ever created a database model, this should be a very familiar concept.
For example, in a blog, your content model will consist of several content types, e.g. posts, writers, and tags. Each of these content types contains attributes. For example, the posts
content type might contain attributes like a title, the text of the post, the author’s name, tags, and a featured image.
Some of these attributes are actually links between different content types. The author of a post, for example, is a separate content type, and the link between the author type and the post type is one of the things described by the content model. One author can be connected to many articles.
Structurally, CSS-Tricks is basically a blog. We can look at the CSS-Tricks website to get an idea of what our content model should look like and what the content types should be.
You can see from the navbar that there are several categories of content: articles, videos, almanac, newsletter, guides, and books. Today, you will be focusing on articles, the most visible category on the site.
If you look at a single article, you will find that it includes some obvious attributes that can be described like this:
Clicking on the author of the article will bring up their page, which will give you an idea of what kind of data the Author type must include:
To create this CSS-Tricks clone, you’ll start from the backend and work your way forward. Starting a new Strapi project is incredibly easy.
cd
into that directory.create-strapi-app
command: npx create-strapi-app backend --quickstart
. Your basic file structure for this app will be a backend
folder for the Strapi app, and a frontend
folder for the Next.js app.[localhost:1337/admin](http://localhost:1337/admin)
to create an administrator account.Create Collection Types
Now you can create your collection types. You’ll start with the Articles type. In the admin panel, go to Plugins > Content-Types Builder. Under Collection Types, click Create new collection type. In the popup, type article under Display Name, then hit Continue. This will bring up options for fields.
Start by clicking Text. Type title in the name field. Click Add another field. Repeat this pattern for all of the fields associated with the Articles type, making sure to choose the correct type for each field as in the screenshot below:
As you saw above, the author and tags fields are links (called relations) to other content types. This is similar to a foreign key in SQL databases. You can’t add those relations until you have the Author and Tag content types, so save your work, and make those next.
Back under Collection Types, click Create new collection type again and go through the steps to create the Author type. Use the image below to see what fields to add.
The articles field is a relation to the Article type. Make sure to select Author has many Articles. This describes the relationship between authors and articles.
Finally, create the Tag type using the same steps as you used for the other content types. Tags only have two fields: tagname and articles, which is a relation to the Articles type.
Articles can have many tags, and tags can be associated with many articles, so make sure that when you create the relation between the two types, you choose Tags has and belongs to many Articles.
Set Permissions
Next, you’ll need to set permissions on your app. In the admin panel, go to General > Settings > Users & Permissions Plugin > Roles > Public. In order for the API to be accessible to users, you will need to check the boxes for find and findone on each content type in the application. Do this and then hit Save.
You can verify that this worked by testing it out using Postman. Send a GET request to [localhost:1337/authors](http://localhost:1337/authors)
. If you’ve set the permissions correctly, you will get a JSON object of authors in the response.
As you know, the beauty of a headless CMS lies in the flexibility to choose how you create the frontend. In this case, you will be using Next.js.
To get started, navigate into your project folder, then use npx create-next-app frontend
to create your Next.js app. Start your app with npm run dev
(while keeping your Strapi backend server running.) Your frontend will be on [localhost:3000](http://localhost:3000)
while the server runs on [localhost:1337](http://localhost:1337)
.
Structuring the Next.js App
At a basic level, your app needs a header and the ability to display cards representing articles. You can create a basic Header component and display it on all pages using _app.js
. Update the pages/_app.js
file by adding the following code to it:
1import '../styles/globals.css';
2import Header from '../components/Header';
3
4function MyApp({ Component, pageProps }) {
5 return (
6 <>
7 <Header />
8 <Component {...pageProps} />
9 </>
10 );
11}
12
13export default MyApp;
Now create an ArticleCa``rd.js
component file in the frontend/components
directory to display data about the articles that you will get from the Strapi backend. Below, you can see one way to construct this component.
1import Link from 'next/link';
2
3const ArticleCard = ({ article }) => {
4 const date = new Date(article.date).toDateString();
5 return (
6 <div className="article">
7 <div className="cover-image">
8 <img src={`http://localhost:1337${article.photo.data.attributes.url}`} />
9 </div>
10 <div className="article-info">
11 {article.tags.data.map((tag, i) => (
12 <Link href={`/tag/${tag.attributes.tagname}`} key={i}>
13 <span className="tags">{tag.attributes.tagname}</span>
14 </Link>
15 ))}
16 <Link href={`/article/${article.slug}`}>
17 <h2>{article.title}</h2>
18 </Link>
19 <div className="article-brief">{article.brief}</div>
20 <p className="author-info">
21 <img src={`http://localhost:1337${article.author.data.attributes.photo.data.attributes.url}`} />
22 <Link href={`/author/${article.author.data.attributes.username}`}>
23 {article.author.data.attributes.name}
24 </Link>{' '}
25 on {date}
26 </p>
27 </div>
28 </div>
29 );
30};
31export default ArticleCard;
On the main page (index.js
), you will be connecting to the Strapi API and mapping the article objects to ArticleCard components.
Connecting to the Strapi API
To fetch articles from the backend, you will simply send a GET
request to the articles
endpoint on the server. You can do this in a getServerSideProps
function:
1import ArticleCard from '../components/ArticleCard';
2const qs = require('qs');
3export default function Home({ articles }) {
4 return (
5 <div className="article-grid">
6 {articles.map((article, i) => (
7 <ArticleCard article={article.attributes} key={i} />
8 ))}
9 </div>
10 );
11}
12export async function getServerSideProps() {
13 const query = qs.stringify({
14 populate: [
15 'tags',
16 'author',
17 'author.photo',
18 'photo'
19 ],
20 }, {
21 encodeValuesOnly: true,
22 });
23
24 const response = await fetch(`http://localhost:1337/api/articles?${query}`);
25 const articles = await response.json();
26 return {
27 props: {
28 articles: articles.data,
29 },
30 };
31}
The Strapi docs have a great explanation for how to access your API from different endpoints. There are automatically created endpoints for all types of CRUD operations.
As can be seen, we are populating some fields, this is because the media and relational fields are not loaded by default. So we need to define the ones we need by populating them.
If you look closer at the code for the ArticleCard component, you can see a couple examples of how data from the Strapi API is accessed.
1 <div className="article-info">
2 {article.tags.data.map((tag, i) => (
3 <Link href={`/tag/${tag.attributes.tagname}`} key={i}>
4 <span className="tags">{tag.attributes.tagname}</span>
5 </Link>
6 ))}
In the code above, the array of tags associated with the article object is mapped over to create those tags on the page.
1<p className="author-info">
2 <img src={`http://localhost:1337${article.author.data.attributes.photo.data.attributes.url}`} />
3 <Link href={`/author/${article.author.data.attributes.username}`}>
4 {article.author.data.attributes.name}
5 </Link>{' '}
6 on {date}
7 </p>
This code uses the article object to access its associated author, then hits that author’s endpoint to retrieve their photo and name.
You can see this in action below:
Styling Your App
The layout of CSS-Tricks is pretty standard for a blog, but there are lots of neat little touches that make it look more polished. The wavy gradient background is done with a clip-path, but you can also use Figma to create a wavy gradient image and place it over the grey background.
The header is partially transparent with an inset box-shadow
, as seen in the CSS below:
1header {
2 position: relative;
3 z-index: 2;
4 display: flex;
5 padding: 1rem;
6 margin: 0;
7 color: #fff;
8 background: rgba(0, 0, 0, 0.25);
9 box-shadow: inset 0 -2px 5px rgb(0 0 0 / 33%);
10}
Finally, the ArticleCards are simple cards displayed using CSS Grid.
1.article-grid {
2 display: grid;
3 grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
4 gap: 2em;
5 margin: 2em;
6}
7.article {
8 background: #fff;
9 color: #474747;
10 border-radius: 8px;
11 position: relative;
12 z-index: 1;
13 overflow: hidden;
14 -webkit-filter: drop-shadow(0 5px 15px rgba(0, 0, 0, 0.24));
15 filter: drop-shadow(0 5px 15px rgba(0, 0, 0, 0.24));
16 display: flex;
17 flex-direction: column;
18}
You can see the finished product below:
As you can see, it’s easy to get started with Strapi. Strapi plus Next.js is an excellent alternative to WordPress. You just created a good start to a CSS-Tricks clone, but there are lots of other features that could be added.
For more practice and to get a better look at the code, fork and clone the repo on GitHub!