Add a headless CMS to Nuxt in 5 minutes

Contents
    Try Storyblok

    Storyblok is the first headless CMS that works for developers & marketers alike.

    In this quick walkthrough, we will have a look at how we can use data from the Storyblok API with a Nuxt project to create a website. At the end of this article, you will have a Nuxt project which renders components filled with data from Storyblok. If you are in a hurry, you may check our Getting Started Guide or Storyblok Nuxt Boilerplate.

    hint:

    Read this article if you ever wondered what a Headless CMS is or what Storyblok does.

    Environment Setup

    Requirements

    To follow this tutorial, here are the following requirements:

    INFO:

    The project in this article was created using the following versions of these technologies:

    • Nuxt Create App v3.6.0
    • Nuxt v10.2.0
    • Nodejs v12.18.0
    • Npm v6.14.9

    Keep in mind that these versions may not be the latest ones.

    Create a new Nuxt project using the nuxt-create-app with the following configuration.

    • JavaScript as the programming language

    • Package manager of your choice (I used Yarn)

    • TailwindCSS as UI Framework

    • Axios module

    • Linting tools of your choice (I used none in the sample project)

    • no testing framework

    • Universal (SSR / SSG) rendering mode

    • Static/JAMStack hosting as a deployment target

    • Development tools of your choice (I used VS Code)

    Afterward, follow the instructions provided in the nuxt-create-app. Install all the modules using the yarn install and run your project the first time with yarn dev. You should see the following result on your http://localhost:3000/.

    Storyblok editing capabilities

    Welcome screen of the Nuxt project after running yarn dev.

    Configuration of the space

    Create a new space in the Storyblok app by clicking + Create new space and choose the Create new space option. Pick a name for it.

    Now, a Storyblok space with sample content has been created. You can see the Home story on your screen.

    hint:

    Understand what represents Story in our Storyblok Guide.

    Enabling the Visual Editor

    If you click on the Home story at this moment, you will not see your Nuxt application. All you need to do to make it work is to open space settings {1}. In the field Location (default environment){2} add your localhost URL as we did on the screenshot.

    Storyblok editing capabilities

    Configuration of the preview URL in Storyblok.

    Open the Home story in the Visual Editor--you still won't see the preview from your localhost. This is because your the Home story has defined slug home and your Nuxt application doesn't know such a path.

    Go to the config tab {1} of your story and override the Real Path {2} in the Visual Preview to "/". The Home story represents the index page so the path should be just simple "/". Don't forget to save your changes.

    Storyblok editing capabilities

    Overriding the real path for the Visual Editor.

    Connecting Nuxt to Storyblok

    We will connect the Storyblok space to our Nuxt project using the storyblok-nuxt module. You can install it into your project using your package manager.

    yarn add storyblok-nuxt

    Installing storyblok-nuxt module in the Nuxt project.

    Next, you will have to register your newly installed module in this config.nuxt.js file. Add the following code into the modules array in the config.nuxt.js file.

    config.nuxt.js
    [
      'storyblok-nuxt',
      {
        accessToken: '[ACCESS-TOKEN]',
        cacheProvider: 'memory'
      }
    ],

    Configuration of the storyblok-nuxt module in config.nuxt.js file.

    Lastly, replace the [ACCESS-TOKEN] with the preview token {2} from your space. You will find your access token in the settings of your space under the APIs tab {1}.

    Storyblok editing capabilities

    Where to get the preview access token of your Storyblok space.

    Loading content & dynamic components

    Before we request our first data load from Storyblok, we should create Nuxt components, which will represent our content in Storyblok.

    At this moment we have default components already defined in Storyblok. You can find their schemas in the components section {1} in Storyblok. By default we created these 4 components {2} for you :

    • feature (nestable component)

    • grid (nestable component)

    • page (content type)

    • teaser (nestable component)

    Open each of them to read about what they do.

    Storyblok editing capabilities

    Default components defined in Storyblok App.

    hint:

    Understand the difference between the nestable components and content type in our Structures of Content tutorial.

    Creation of Nuxt components

    Create the following components in the ./components folder of your project.

    ./components/Feature.vue
    <template>
      <div
        v-editable="blok"
        class="py-2">
        <h1 class="text-lg">{{ blok.name }}</h1>
      </div>
    </template>
    
    <script>
    export default {
      props: {
        blok: {
          type: Object,
          required: true
        }
      }
    }
    </script>
    

    Vue source code of the Feature component

    ./components/Grid.vue
    <template>
      <ul
        v-editable="blok"
        class="flex py-8 mb-6">
        <li
          :key="blok._uid"
          v-for="blok in blok.columns"
          class="flex-auto px-6">
          <component :blok="blok" :is="blok.component" />
        </li>
      </ul>
    </template>
    
    <script>
    export default {
      props: {
        blok: {
          type: Object,
          required: true
        }
      }
    }
    </script>
    

    Vue source code of the Grid component

    ./components/Page.vue
    <template>
      <div
        v-editable="blok"
        class="px-6">
        <component
          v-for="blok in blok.body"
          :key="blok._uid"
          :blok="blok"
          :is="blok.component" />
      </div>
    </template>
    
    <script>
    export default {
      props: {
        blok: {
          type: Object,
          required: true
        }
      }
    }
    </script>
    

    Vue source code of the Page component

    ./components/Teaser.vue
    <template>
      <div
        v-editable="blok"
        class="py-8 mb-6 text-5xl font-bold text-center">
        {{ blok.headline }}
      </div>
    </template>
    
    <script>
    export default {
      props: {
        blok: {
          type: Object,
          required: true
        }
      }
    }
    </script>
    

    Vue source code of the Teaser component

    We have to also register these components in the Nuxt app. All of these are dynamic and our API response will decide which component will be rendered in which case. Create file components.js in your plugins folder with the following content.

    ./plugins/components.js
    import Vue from 'vue'
    import Page from '~/components/Page.vue'
    import Teaser from '~/components/Teaser.vue'
    import Grid from '~/components/Grid.vue'
    import Feature from '~/components/Feature.vue'
    
    Vue.component('page', Page)
    Vue.component('teaser', Teaser)
    Vue.component('grid', Grid)
    Vue.component('feature', Feature)
    

    Registration of the components in the Nuxt app using the plugin approach.

    Register the plugin in the ./config.nuxt.js by adding the following code '~/plugins/components' in the plugins array. Don't forget to save your changes.

    Load content from Storyblok

    Finally, we load the content from Storyblok.

    Create _.vue file in the pages folder with the following content and remove the index.vue file. The _.vue file will resolve all the dynamic pages for us.

    ./pages/_.vue
    <template>
      <page
        v-if="story.content.component"
        :key="story.content._uid"
        :blok="story.content" />
    </template>
    
    <script>
    export default {
      data () {
        return {
          story: { content: {} }
        }
      },
      mounted () {
        this.$storybridge(() => {
          const storyblokInstance = new StoryblokBridge()
    
          // Listen to Storyblok's Visual Editor event
          storyblokInstance.on(['input', 'published', 'change'], (event) => {
            if (event.action == 'input') {
              if (event.story.id === this.story.id) {
                this.story.content = event.story.content
              }
            } else {
              this.$nuxt.$router.go({
                path: this.$nuxt.$router.currentRoute,
                force: true,
              })
            }
          })
        }, (error) => {
          console.error(error)
        })
      },
      asyncData (context) {
        // We are getting only the draft version of the content in this example.
        // In real world project you should ask for correct version of the content
        // according to the environment you are deploying to.
        // const version = context.query._storyblok || context.isDev ? 'draft' : 'published'
    
        const fullSlug = (context.route.path == '/' || context.route.path == '') ? 'home' : context.route.path
    
        // Load the JSON from the API - loadig the home content (index page)
        return context.app.$storyapi.get(`cdn/stories/${fullSlug}`, {
          version: 'draft'
        }).then((res) => {
          return res.data
        }).catch((res) => {
          if (!res.response) {
            console.error(res)
            context.error({ statusCode: 404, message: 'Failed to receive content form api' })
          } else {
            console.error(res.response.data)
            context.error({ statusCode: res.response.status, message: res.response.data })
          }
        })
      }
    }
    </script>
    

    Added dynamic route to our Nuxt project.

    hint:

    Read the official Nuxt documentation to understand how the dynamic pages are working in Nuxt.

    You should see the following design on http://localhost:3000/ after your Nuxt project will recompile all the changes.

    Storyblok editing capabilities

    Localhost preview after all necessary steps.

    Loading content using asyncData hook

    In this tutorial, we fetch data using the Nuxt asyncData hook (you can use also a different approach to load data), and you will use the installed $storyapi to request content of the story of your choice.

    Sample asyncData function
    asyncData (context) {
        // We are getting only the draft version of the content in this example.
        // In real world project you should ask for correct version of the content
        // according to the environment you are deploying to.
        // const version = context.query._storyblok || context.isDev ? 'draft' : 'published'
    
        const fullSlug = (context.route.path == '/' || context.route.path == '') ? 'home' : context.route.path
    
        // Load the JSON from the API - loadig the home content (index page)
        return context.app.$storyapi.get(`cdn/stories/${fullSlug}`, {
          version: 'draft'
        }).then((res) => {
          return res.data
        }).catch((res) => {
          if (!res.response) {
            console.error(res)
            context.error({ statusCode: 404, message: 'Failed to receive content form api' })
          } else {
            console.error(res.response.data)
            context.error({ statusCode: res.response.status, message: res.response.data })
          }
        })
      }

    Example asyncData function from this tutorial.

    In this sample, we find the name of the story using the slug (URL path). You can see that we get it from the Nuxt context (context.route.path). If we are on the index page (path equals "/") we are manually defining the slug to home.

    Real-time editing using the Editable directive

    You may notice dashed and green lines in the Visual Editor or even the context menu, or real-time updates as you are editing the text or changing the positions of the components. If not, check out the following screenshot and try it yourself in the Storyblok app.

    Storyblok editing capabilities

    Real-time context visual editing in the Storyblok app.

    This content editing enhancement works thanks to the storyblok-brige and the storyblok-nuxt module. In the Nuxt case, it requires that you will use the editable directive v-editable on all of the editable components in Storyblok . This will bridge the Storyblok app with the Nuxt app. It works only in the draft mode using the additional identifiers added into the draft JSON of your content. Thanks to this your production JSON will be slim as possible.

    ./components/Grid.js
    <template>
      <ul
        v-editable="blok"
        class="flex py-8 mb-6">
        <li
          :key="blok._uid"
          v-for="blok in blok.columns"
          class="flex-auto px-6">
          <component :blok="blok" :is="blok.component" />
        </li>
      </ul>
    </template>
    
    <script>
    export default {
      props: {
        blok: {
          type: Object,
          required: true
        }
      }
    }
    </script>
    

    Sample of v-editable in use.

    The "blok" prop in this case represents all the data of the requested component.

    What is great about this approach is that you can dynamically decide which components will be rendered in what order. This is thanks to the information you have in each component object and using the dynamic vue component.

    hint:

    Learn more about how the Storyblol-Bridge works in our Developer Guides.

    Summary

    Using Storyblok as your CMS with Nuxt is as simple as loading JSON from an API. Since the content provided is structured in components - you can directly use the setup prepared by Nuxt and Vue. The visual editor can easily be plugged in and is only needed for that instant preview, not for production.

    NEXT:

    We (Storyblok) are huge fans of the Nuxt and the Vue.js universe. The Storyblok app is built in Vue.js and we are proud of it. Because of this and other reasons we put together multiple resources on how to learn Nuxt and how to use it with Storyblok. These should be your next steps. We wish you a wonderful Storyblok-Nuxt journey.

    Developer Newsletter

    Want to stay on top of the latest news and updates in Storyblok?
    Subscribe to Code & Bloks - our headless newsletter.

    An error occurred. Please get in touch with support@storyblok.com

    Please select at least one option.

    Please enter a valid email address.

    This email address is already registered.

    Please Check Your Email!

    Almost there! To confirm your subscription, please click on the link in the email we’ve just sent you. If you didn’t receive the email check your ’junk folder’ or