Introduction to Headless CMS
Re-inventing the wheel has always been tedious, and it prolongs the distance between idea and production. Headless CMS helps developers, marketing teams and other creative professionals move from idea to production with speed because you don’t have to worry about anything server related since it is taken care of already. A headless CMS can connect to any frontend (hence the name headless). It gives you the liberty to use any frontend library/framework of choice, unlike the traditional CMSs which are coupled thereby constraining you to a particular frontend.
A headless CMS primarily acts as a content repository where you can store data and retrieve it easily through a content delivery API which could be a GraphQL API or a REST API. The data retrieved via the API can be displayed on any device of your choice. This will enable you to deliver content seamlessly to any device - web, mobile app, IoT, electronic billboards, etc.
Why Storyblok
In choosing a headless CMS platform, there are certain things to consider: Scalability, freedom & flexibility, developer experience, omnichannel publishing, performance, security, etc. Storyblok is cut out for this with its built-in features optimized to meet your product needs. Storyblok provides you the flexibility of building with tech stacks that you are familiar with because it supports all frameworks - you do not need to use any technology that is unfamiliar to you. Its powerful SDKs help you connect and build seamlessly. Later in this article, we shall explore one of the SDKs, the React SDK, in building a Note app.
Furthermore, with Storyblok's real-time visual editor you can see a preview when creating or editing content. You would also have the liberty of exploring the components of your page. This feature can be enabled and disabled per your need. Other benefits of Storyblok include fast performance, scalability, easy team collaboration, built-in features, etc.
Next up in this article, we will learn how to tap into the power of Storyblok to build an application - a Note taking app.
Requirements
To follow this article you need:
- Node and npm installed on your machine
- Next.js 12.
- A Storyblok account
- Any code editor of your choice
Now, let’s set up our Storyblok project and begin building.
Setting Up and Configuring Our Storyblok Project
Before creating a new space on Storyblok, it is important to define some terms you’d come across in this article.
- Space: A space is a content repository. You can create as many spaces as you want. Ideally, you’d need just a space per project. A space holds everything for your project - folder, assets, block, stories, etc.
- Story: Stories are content entries that utilize any selected template as their content type.
- Folder: A folder is where you keep related stories
- Block: Blocks are what makes up stories. They are modular components put together to form content in its entirety. There are 3 block types in Storyblok: Nestable block, Content-Type block, and Universal block.
You will see them in action in a bit. What we need to do now is to create a space and set up what we’d need for our application.
- Create a new space and give it a name. Here we gave it the name Note Application and selected the server location as EU.
Creating Blocks
Let’s go over and create some blocks that we’d be needing for our application.
You should remove all the default blocks except the page block because we would not need them. Let's create some fresh blocks 😉
In addition to the page block, we need to create 2 more blocks:
-
Note: The Note block will take 3 fields:
title,
body
andcreated_at
-
Note-highlights: this will be a Nestable block and takes a field name
note
of typeMulti-Options
. We want Note-highlights to display a few notes on the homepage - each note will be clickable. It will take you to the entire note when you click.
Click on the note
field then configure the source
for the Multi-Option field to point to Stories
. This is where we want to read the data from. Make the path to folder of story
to be notes/
and also restrict content-type to the note
block. We will create the note
folder shortly.
Our blocks are set. Now let’s create the notes folder which will hold all our notes and also make the Home
story display the Note-highlights
page.
Creating the Notes Folder
Navigate inside Content > click Create new > select Folder. Give it a name of Notes and make the Content type Note (we want it to use the Note block we created earlier as its content type).
Setting Up The Homepage
When you navigate into Content, you’d observe that Storyblok gives us a Homepage by default. We will make this Homepage display the Note-highlights page.
Let’s add the Note-highlights
page to Home
You’d observe that our development server is pointing to https://quickstart.me.storyblok.com
. We need to set up an HTTPS proxy for our dev server and make port 3010 the port for our application. The URL to access our application will be https://localhost:3010/
. To do this, go to Settings > Visual Editor then change the URL of the local environment to https://localhost:3010/
Everything is set here in Storyblok. Now, let’s build the frontend of our application with Next.js.
Creating A Next.js Project
npx create-next-app note-app
Go ahead and install Storyblok’s React SDK. Also, we need to install Storyblok rich text renderer.
npm install @storyblok/react storyblok-rich-text-react-renderer
Connecting Our Application To Storyblok
Let’s connect our Next.js application to Storyblok
pages/_app.js
import {storyblokInit, apiPlugin} from "@storyblok/react"
import Page from "../components/Page"
import Note from "../components/Note"
import NoteHighlights from "../components/NoteHighlights"
const components = {
page: Page,
note: Note,
"note-highlights": NoteHighlights,
}
storyblokInit({
accessToken: "Your-access-token",
components,
use: [apiPlugin]
})
export default function App({ Component, pageProps }) {
return <Component {...pageProps} />
}
The storyblokInit
function initializes Storyblok and connects to it via your accessToken
. It makes use of the apiPlugin
. We added the Storyblok components that we will be utilizing in our app. We will create the components inside our project shortly.
Replace the value of accessToken
with your preview token found inside Settings > Access Tokens.
Fetching Data From Storyblok
At this point, we want to set up our landing page (otherwise called homepage). To do this, we have to fetch data from our Storyblok content repository
pages/index.js
import Head from 'next/head'
import {StoryblokComponent, getStoryblokApi, useStoryblokState} from "@storyblok/react"
export default function Home({story}) {
story = useStoryblokState(story, {
resolveRelations: ["note-highlights.note"],
});
return (
<div>
<Head>
<title>Note app</title>
<meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div>
<StoryblokComponent blok={story.content} />
</div>
</div>
)
}
export async function getStaticProps(){
let slug = "home";
let sbParams = {
version: "draft", // it can also be "published"
resolve_relations: ["note-highlights.note"]
}
const storyblokApi = getStoryblokApi()
let {data} = await storyblokApi.get(`cdn/stories/${slug}`, sbParams)
return {
props: {
story: data ? data.story : false,
key: data ? data.story.id : false
},
revalidate: 3600,
}
}
StoryblokComponent
sets up a page for our Storyblok components and getStoryblokApi
fetches data from Storyblok through your API endpoint.
useStoryblokState
is a custom hook that connects the current page to Storyblok’s real-time visual editor.
We want to use the Next.js Static Site Rendering feature hence the getStaticProps
function we called our API in. Our API request takes in two parameters: version
, where you want Storyblok to read the data from (in this case from your Draft) and resolve_relations
which gets all the details related to the note-highlights.note
block.
revalidate
allows us to update our page (in this case our homepage) without rebuilding our entire site. We are revalidating every 3600 milliseconds (1 hour).
Dynamic Routing
We are going to create a catch-all route to capture all the individual notes.
Create a [...slug].js
file inside the pages directory
[...slug].js
import React from 'react';
import Head from "next/head"
import { StoryblokComponent, getStoryblokApi } from '@storyblok/react';
const Page = ({story}) => {
return (
<div>
<Head>
<link rel="icon" href="/favicon.ico" />
</Head>
<StoryblokComponent blok={story.content} />
</div>
);
}
export default Page
export async function getStaticProps({params}) {
let slug = params.slug ? params.slug.join("/") : "home"
let sbParams = {
version: "draft",
// resolve_relations: ["note-highlights.note"]
};
const storyblokApi = getStoryblokApi()
let {data} = await storyblokApi.get(`cdn/stories/${slug}`, sbParams)
return {
props: {
story: data ? data.story : false,
key: data ? data.story.id : false
},
revalidate: 3600
};
}
export async function getStaticPaths() {
let sbParams = {
version: "draft",
};
const storyblokApi = getStoryblokApi()
let {data} = await storyblokApi.get(`cdn/links/`, sbParams)
let paths = [];
Object.keys(data.links).forEach((linkKey) => {
if (data.links[linkKey].is_folder || data.links[linkKey].slug === "home") {
return;
}
const slug = data.links[linkKey].slug;
let splittedSlug = slug.split("/");
paths.push({ params: { slug: splittedSlug } });
});
console.log(paths)
return {
paths,
fallback: false
};
}
getStaticPaths
gets the path for all our notes so that we can route them dynamically. Note that we specified that it should not get the paths of folders and the homepage.
Building Our Components
Recall that we added the Pages
, Note
, and Note-Highlights
components to our Storyblok connection. Now, let’s build them. You should create these components inside the root of your project.
Pages.js
import React from 'react'
import { StoryblokComponent, storyblokEditable } from '@storyblok/react'
function Page({blok}) {
return (
<main {...storyblokEditable(blok)}>
{blok.body.map((nestedBlok) => (
<StoryblokComponent blok={nestedBlok} key={nestedBlok._uid}/>
))}
</main>
)
}
export default Page
StoryblokEditable
lets you edit this page inside Storyblok’s real-time Visual Editor.
Note-Highlights.js
import React from 'react'
import { storyblokEditable } from '@storyblok/react'
import { render } from 'storyblok-rich-text-react-renderer';
import Link from "next/link";
function NoteHighlights({blok}) {
return (
<div {...storyblokEditable(blok)} key={blok._uid}>
<h2 className='my-header'> My Notes</h2>
{blok.note.map(note => {
return(
<div key={note.id} className="note-highlight">
{/* Link to full note */}
<Link href={`/${note.full_slug}`} key={note.id}>
<h2>{note.content.title}</h2>
</Link>
<div className="line-clamp-4">
{render(note.content.body)}
</div>
<Link href={`/${note.full_slug}`}><p className="note-link">Read more</p></Link>
</div>
)
})}
</div>
)
}
export default NoteHighlights
This page displays some of our notes. We used line-clamp
which is a Tailwind CSS property to display only a few lines of a paragraph. The link will take us to the full_slug
(where you can see the entire note).
Note.js
import React from 'react'
import { storyblokEditable } from '@storyblok/react'
import {render} from "storyblok-rich-text-react-renderer"
import Link from 'next/link'
function Note({blok}) {
return (
<div {...storyblokEditable(blok)} key={blok._uid}>
<div>
<div className="note-container">
<h2>{blok.title}</h2>
<div className='render-note'>
{render(blok.body)}
</div>
</div>
<p className='createdAt'>{blok.created_at}</p>
</div>
<button className='bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded ml-2 mb-5'>
<Link href="/">
Back
</Link>
</button>
</div>
)
}
export default Note
Let’s go over and add some notes inside Storyblok.
Navigate to Content > go inside the Note folder we created earlier > click Create new > select Story.
Give it a name make the Content type Note. Click Create when you are done.
Add a few more notes and let’s take a look at our application.
Voila! Our application looks great. If you click the Read more text on any note, it takes you to the full slug of the note - where you’d see the entire note.
Summary
Using the Storyblok Headless CMS, you can move from idea to production in a short time. Storyblok connects with all frameworks and therefore helps you build seamlessly. The real-time Visual Editor enables you to make live edits that immediately reflect in your application. Storyblok has great documentation to get you started with headless CMS development.
Do you need help creating technical content for your developer audience? Reach out and let’s work together.
Shameless Plug
If you find my content valuable and engaging, consider sharing it with your network and follow me here and on Twitter. It would mean a lot to me.
I just launched my newsletter where I’ll be sharing carefully curated technical articles and development news. If you enjoy learning about Open source software, Web engineering, Software engineering, Cloud & DevOps and other aspects of product engineering, please join my readers’ list.
Top comments (0)