DEV Community

Cover image for Building a Note taking app with Storyblok and Next.js
Emmanuel Aiyenigba
Emmanuel Aiyenigba

Posted on

Building a Note taking app with Storyblok and Next.js

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

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.

create a new space

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

  • 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

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

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

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

insert block

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/

Visual editor

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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} />
}
Enter fullscreen mode Exit fullscreen mode

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

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, 
  }
}
Enter fullscreen mode Exit fullscreen mode

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
    };
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

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

displaying notes

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.

Emmanuel’s Substack | Emmanuel Aiyenigba | Substack

Carefully curated engineering articles, open source software updates and development news worth knowing about. Click to read Emmanuel’s Substack, by Emmanuel Aiyenigba, a Substack publication. Launched a year ago.

favicon emmanuelthecoder.substack.com

Top comments (0)