Building a Note taking app with Storyblok and Next.js
Tags: nextjs, storyblok, webdev, tutorial
Published: 2023-09-05 09:12:18

## 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. 


![storyblok and next.js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eu3ybvxfizefdzz337bb.png)



## 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](https://www.storyblok.com/mp/announcing-storyblok-react), 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**.

 


![create a new space](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pvigy7a8zicenvdh2etu.png)




## 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` and `created_at`


![create block](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fl75bwvowxvwzoktr9wc.png)



- **Note-highlights**: this will be a **Nestable block** and takes a field name `note` of type `Multi-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.


![create Note-highlights block](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qxn8kck5q4vkccopvj6s.png)



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.


![edit block](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8hammvsh3nzwhrotgl5r.png)



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).


![Create new folder](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qybw4xfkutgezrtytsyl.png)



## 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`


![adding Note-highlights to Homepage](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yr9wlymmcrq1gj4g3ums.png)


![insert block](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1t8asvh4cq4yawka6ps7.png)



You’d observe that our development server is pointing to `https://quickstart.me.storyblok.com` . We need to set up an [HTTPS proxy](https://www.storyblok.com/faq/setup-dev-server-https-windows) for our dev server and make port 3010 the port for our application. The URL to access our application will be [`https://localhost:3010/`](https://localhost:3010/). To do this, go to **Settings** > **Visual Editor** then change the URL of the local environment to [`https://localhost:3010/`](https://localhost:3010/)


![Visual editor](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nltyjpxujybyost41mgu.png)


Everything is set here in Storyblok. Now, let’s build the frontend of our application with Next.js.

## Creating A Next.js Project

```jsx
npx create-next-app note-app
```

Go ahead and install Storyblok’s React SDK. Also, we need to install Storyblok rich text renderer. 

```jsx
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`

```jsx

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.** 

![access tokens](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vmqn8nggm3yr9wes8p7e.png)



## 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`

```jsx
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`

 

```jsx
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`

```jsx
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`

```jsx
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`](https://tailwindcss.com/blog/multi-line-truncation-with-tailwindcss-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`

```jsx
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.

![create story](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g43frfkpirvl7b6tvqip.png)


Add a few more notes and let’s take a look at our application.

![displaying notes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3eo6vijxzjz399dofc0l.png)



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](https://www.storyblok.com/docs/guide/getting-started) to get you started with headless CMS development. 

***Do you need help creating technical content for your developer audience? [Reach out](mailto:loyaltysamuel001@gmail.com) 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](https://twitter.com/EmmanuelCoder). 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](https://emmanuelthecoder.substack.com/).


{% embed https://emmanuelthecoder.substack.com/embed %}