Contents

Stories, Chapters and Paragraphs: Structuring Content with Storyblok and Vue.js

Structuring the content for complex sites like homepages and landing pages, can be a very challenging task. For the longest time most content management systems weren’t really well suited for dealing with complex content structures. With the rise of headless content management systems things have changed pretty fast. The decoupling of the data layer and the rendering layer makes it easier to forge complex data structures on the one side and render the data on the frontend with full control over the markup.

Thanks to the concept of Components and Blocks, Storyblok is even more powerful in that regard than most other headless content management systems. In this article, we’ll take a look at how we can structure the content for complex landing pages with Storyblok in a way that we can reuse a lot of the building blocks. This makes it possible to build multiple landing pages, with different layouts but the same basic building blocks, in no time. Additionally, we will build a simple Vue.js app that will make it very easy for us to render our complex data.

The building blocks: Stories, Chapters and Paragraphs

Inspired by the “Story” naming pattern I’ve come up with two extra ingredients for crafting stories: Chapters and Paragraphs.

Every building block, no matter if Story, Chapter or Paragraph, is a Storyblok Component.

List of Storyblok components

Stories

Like a real story, Stories usually have one or multiple Chapters. Stories in Storyblok are content types. When following the methodology of this article, usually the only field of a Story is a field named chapters which is a field of type Blocks referencing one or multiple Chapter components, but you might also add additional fields for meta informations directly to the Story component.

Chapters

Chapters are the core components of any Story. In our system all the main pieces of content like text or images, must live inside of a Chapter or a Paragraph. So each chapter can have one or multiple fields. Furthermore, chapters can (but don’t have to) consist of multiple paragraphs.

The main reason why we differentiate between Chapters and Paragraphs is that Chapters control the layout of the piece of content. So Chapters must have a layout configuration field.

The layout configuration component

In this example, our Chapter layout configuration is responsible for two things: the width of the Chapter and the spacing around it. But you could add more configuration options like the possibility to change the background color for example.

Our chapter configuration is a separate component. We create it by adding a new component with the name of config.

Creating a new configuration Storyblok component

Next we add two new fields to the schema of our newly created configuration component: a spacing_top field, which makes it possible to configure the the spacing between two chapters and a field named container which we’ll use to control the width of Chapters.

spacing_top field configuration

container field configuration

As you can see in the screenshots above, the two fields of our configuration component, are configured exactly the same. Both are Single-Options fields with three options: Small, Medium and Large. In the case of the spacing_top field those options represent the size of the spacing which is added to the top of the Chapter. The value of the container field determines the width of the Chapter.

Paragraphs

Paragraphs are very similar to Chapters with the main difference being that Paragraphs do not have a layout configuration field. Typical use cases for Paragraphs are teaser blocks or other highly reusable building blocks.

Building a landing page story

After defining all the available categories of building blocks which we have at our disposal, let’s actually build a landing page story.

Creating a new landing page Story

Our landing page Story component is rather simple: it only contains one field for referencing the Chapters which make the Story.

Adding Chapters

To demonstrate the power of this methodology we create three Chapters. An Intro Chapter for rendering a headline and some large intro text. A Media Object Chapter for rendering combinations of images and text. And last but not least a Multi Column Teaser Chapter which we’ll use to display a list of Card Paragraphs.

Website with Intro, Media Object and Multi Column Teaser Chapters

In the screenshot above, you can see all three Chapters. The headline + text combination is the Intro Chapter. The Intro Chapter is followed by the Media Object Chapter after which you can see the Multi Column Teaser Chapter.

The Intro Chapter

We want our landing pages to start off with a headline and a short introduction about the topic of our landing page. Let’s add a new chapter_intro component which makes this possible.

Creating a Intro Chapter component

As you can see above, our Intro Chapter component consists of three fields: headline, text and config. The first two fields are simple text fields but let’s take a closer look at the settings of the config field.

Intro Chapter config field

The config field is of the type Blocks. But we restrict the set of components to only allow the config component to be added. Also only a single config component is allowed and the user is required to add one config component.

The Media Object Chapter

Next on the line is the Media Object Chapter. We want this chapter to contain an image field and a field containing some text. Both fields use the default options. Again the config field uses the same options as we’ve already seen previously in the Intro Chapter.

Creating a new Media Object Chapter component

Multi Column Teaser Chapter

Last but not least we create the Multi Column Teaser Chapter. We want this Chapter component to render multiple Card Paragraph components. So let’s create the Card Paragraph component first.

Creating a new Card Paragraph component

The Card Paragraph component consists of three simple fields: an image, headline and a text field. All of those fields use the default options.

Now we’re ready to create our Multi Column Teaser Chapter component.

Creating a new Multi Column Teaser Chapter component

The Multi Column Teaser component consists of only two fields: the teasers which is of type Blocks and a config field which uses the same options as we’ve already seen in the Intro Chapter. In the screenshot below you can see the teasers fields configuration options. We restrict the set of components to only paragraph_card and we allow a maximum of 3 components to be added.

Multi Column Teaser Chapter teasers field

Writing a Story

Now we have all the necessary building blocks at our disposal to create a new Landing Page Story. First we create a new content of type Landing Page.

Creating a new Landing Page

Next we can add new Chapters to our Story. As you can see in the following screenshot, the Chapters we’ve created earlier are now available as our contents for creating Stories.

Adding Chapters to a Story

The final Story with Chapters

Rendering Stories in Vue.js

After writing our story, we’re ready to render it in our frontend of choice. In the following example I’ll use Vue.js but you can take the basic idea and build something similar with any frontend or backend framework you like.

<template>
  <div class="ChapterLoader">
    <ChapterLayout
      v-for="chapter in chapters"
      :key="chapter._uid"
      :config="chapter.config[0]"
    >
      <Component
        :is="`${chapter.component.replace(/_/g, '-')}`"
        :blok="chapter"
      />
    </ChapterLayout>
  </div>
</template>

<script>
import ChapterLayout from './ChapterLayout.vue';

const ChapterIntro = () => import('./chapters/ChapterIntro.vue');
const ChapterMediaObject = () => import('./chapters/ChapterMediaObject.vue');
const ChapterMultiColumnTeaser = () => import('./chapters/ChapterMultiColumnTeaser.vue');

export default {
  name: 'ChapterLoader',
  components: {
    ChapterIntro,
    ChapterLayout,
    ChapterMediaObject,
    ChapterMultiColumnTeaser,
  },
  props: {
    chapters: {
      required: true,
      type: Array,
    },
  },
};
</script>

In the code snippet above you can see the ChapterLoader component which is responsible for loading the given Chapter components. Here you can see that we’re dynamically importing the matching Chapter components for which we defined the data structures in Storyblok earlier.

<template>
  <div
    :class="classes"
    class="ChapterLayout"
  >
    <slot/>
  </div>
</template>

<script>
export default {
  name: 'ChapterLayout',
  props: {
    config: {
      type: Object,
      default: () => ({}),
    },
  },
  computed: {
    classes() {
      const classes = [];

      if (this.config.spacing_top) {
        classes.push(`u-spacing-top-${this.config.spacing_top}`);
      }

      if (this.config.container) {
        classes.push(`o-container o-container--${this.config.container}`);
      }

      return classes;
    },
  },
};
</script>

The ChapterLayout component you can see above, takes the data from the config data component and applies u-spacing-top-* and o-container--* classes to the Chapter layout wrapper. The u-spacing-top-* utility class is responsible for applying margin-top to the Chapter. The o-container--* class limits the width of the Chapter.

If you want to take a closer look at the rest of the Vue.js components, you can play around with the following CodeSandbox.

Wrapping it up

If you’re thinking about structuring the content of your Storyblok project in a similar way as we did above, keep in mind that this approach is designed for more complicated websites. If, what you build is a very simple site and you don’t need your content editors to be able to build complex layouts themselves, you might be better off keeping it as simple as possible.

On the other hand, if you want your content editors to have full control over the layout of new landing pages they build completely by their own, this approach might be a powerful solution.


More to read...

About the author

Markus Oberlehner

Markus Oberlehner

Markus Oberlehner is a Open Source Contributor and Blogger living in Austria and working at karriere.at. He is the creator of avalanche and node-sass-magic-importer.