Add a headless CMS to VueJS in 5 minutes

Contents
    Try Storyblok

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

    In this short article, we’ll have a look at how we can use data from the Storyblok API with a Vue.js project to create a website. At the end of this article, you will have a Vue.js Application that renders components filled with data from Storyblok.

    In case you don’t know what’s a Headless CMS or what Storyblok does, please read first the Storyblok Guide.

    LIVE DEMO:

    If you’re in a hurry, try yourself the live demo in Stackblitz!

    Environment Setup

    Requirements

    To follow this tutorial make sure to meet these requirements:

    • Basic understanding of Vue.js and Javascript

    • Node.js LTS version

    • An account in the Storyblok App

    Create a Vue.js project

    Let’s use Vue.js version 3 for this tutorial, since it is now the new default.

    Following the Vue.js official installation guide, we can create our project using the official Vue project scaffolding tool: create-vue. It’s Vite based and uses all new recommendations, meaning you’ll get an incredible DX. Use it by running:

    npm init vue@latest

    Let's choose the following options:

    • Add TypeScript? No

    • Add JSX Support? No

    • Add Vue Router for Single Page Application development? No

    • Add Pinia for state management? No

    • Add Vitest for Unit Testing? No

    • Add Cypress for both Unit and End-to-End testing? No

    • Add ESLint for code quality? No

    Once you install the dependencies and run npm run dev in the project folder, you’ll see this screen when you open http://localhost:3000 in your browser:

    https://localhost:3000
    Storyblok editing capabilities

    Welcome screen of the Vue project after running npm run dev.

    Configuration of the space

    Create a new space in the Storyblok app by choosing the Create new space {1} option. Pick a name for it {2}.

    Storyblok editing capabilities
    1
    2

    Create a new space in Storyblok

    Now, a Storyblok space with sample content has been created. If you open the Home story you will see the Visual Editor on your screen:

    storyblok.com
    Default screen on the Home story of the Visual Editor

    Default screen on the Home story of the Visual Editor

    hint:

    Understand what represents Story in our Storyblok Guide.

    Enabling the Visual Editor

    To see your website in Storyblok Visual Editor you need to set the default environment URL. For that, in your space open Settings > Visual Editor {1} and set the Location field to https://localhost:3000 {2}:

    storyblok.com
    Setting a default environment URL
    1
    2

    Setting a default environment URL

    Storyblok v2 requires that your app is served via HTTPS. Luckily, this is very easily achievable by setting the following option your vite.config.js:

    server: {
      https: true,
    },

    Now go back to the Home story under the Content section. When you open it, you’ll see the Visual Editor, but you won’t see yet your Vue application in there.

    Just open the Entry configuration {1} on the right-hand form, and set the Real Path to “/” {2}. Save, and if you’re still running your Vue app you will see it now in the Visual Editor.

    Storyblok editing capabilities
    1
    2

    Overriding the real path for the Home story

    Connecting Vue to Storyblok

    First of all, let’s install @storyblok/vue, our official SDK for Vue 3:

    npm install @storyblok/vue

    The SDK allows you to interact with Storyblok API and enable the real-time editing experience. Let's configure it.

    First of all, you need to grab your API token from your space Settings > API-Keys:

    storyblok.com
    Storyblok editing capabilities
    1
    2
    3

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


    Then use the preview API token in src/main.js to like in this example:

    src/main.js
    import { createApp } from 'vue';
    import { StoryblokVue, apiPlugin } from '@storyblok/vue';
    import App from './App.vue';
    
    const app = createApp(App);
    
    app.use(StoryblokVue, {
      accessToken: 'OurklwV5XsDJTIE1NJaD2wtt',
      bridge: process.env.NODE_ENV !== 'production', // optimizes by excluding the bridge on production
      use: [apiPlugin],
    });
    
    app.mount('#app');

    Displaying Components on the Vue App

    The idea when using Storyblok for this case is:

    • A content manager (even if it’s yourself) can create pages (or stories) composed of different sections, called components.

    • As a developer, you get the page as a JSON structure using Storyblok API and render the components (which you have to implement in your Vue App).

    When you create a space, Storyblok creates 4 components by default for you:

    • feature (nestable component)

    • grid (nestable component)

    • teaser (nestable component)

    • page (content type)

    You can find them all in the Components section of your Storyblok space.

    hint:

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

    Create Vue Components

    Let’s implement the previous 4 components in your Vue App, right under the components folder.

    components/Feature.vue
    <template>
      <div v-editable="blok" class="py-2">
        <h1 class="text-lg">{{ blok.name }}</h1>
      </div>
    </template>
    
    <script setup>
    defineProps({ blok: Object })
    </script>
    components/Grid.vue
    <template>
      <div v-editable="blok" class="flex py-8 mb-6">
        <div v-for="blok in blok.columns" :key="blok._uid" class="flex-auto px-6">
          <StoryblokComponent :blok="blok" />
        </div>
      </div>
    </template>
    
    <script setup>
    defineProps({ blok: Object })
    </script>
    components/Page.vue
    <template>
      <div v-editable="blok" class="px-6">
        <StoryblokComponent
          v-for="blok in blok.body"
          :blok="blok"
          :key="blok._uid"
        />
      </div>
    </template>
    
    <script setup>
    defineProps({ blok: Object })
    </script>
    components/Teaser.vue
    <template>
      <div v-editable="blok" class="py-8 mb-6 text-5xl font-bold text-center">
        {{ blok.headline }}
      </div>
    </template>
    
    <script setup>
    defineProps({ blok: Object })
    </script>

    You can see in their template that the Teaser.vue component has a headline property, and that Feature.vue has a name.

    The question is: how do you know what properties have a blok? You can check the component schemas to find out.

    Finally, make these components available by loading them from main.js:

    main.js
    import App from "./App.vue";
    import Grid from "./components/Grid.vue";
    import Page from "./components/Page.vue";
    import Teaser from "./components/Teaser.vue";
    import Feature from "./components/Feature.vue";
    
    const app = createApp(App);
    // ...
    app.component("Grid", Grid);
    app.component("Page", Page);
    app.component("Teaser", Teaser);
    app.component("Feature", Feature);

    Adding TailwindCSS

    As you may have noticed, the components we just created utilize Tailwind classes. For the sake of simplicity, let's quickly add Tailwind by importing it in index.html right before the closing head tag:

    index.html
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <link rel="icon" href="/favicon.ico" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Vite App</title>
        <script src="https://cdn.tailwindcss.com"></script>
      </head>
      <!-- ... -->
    </html>

    Load content using the API

    Now that we have our components ready, it’s time to get the Home story data. You can view the JSON structure of any story by clicking the Draft JSON button in the Visual Editor.

    storyblok.com
    Storyblok editing capabilities
    1
    2

    Get Draft JSON from Visual Editor

    From the code side, first create a pages/Home.vue component that uses the useStoryblokApi from @storyblok/vue to fetch the content:

    pages/Home.vue
    <script setup>
    import { useStoryblok } from '@storyblok/vue';
    const story = await useStoryblok('home', { version: 'draft' });
    </script>
    
    <template>
      <StoryblokComponent v-if="story" :blok="story.content" />
    </template>

    Now import it in your App.vue. Because we’re using top-level await in Home.vue, you need to use Suspense, a Vue 3 feature that allows you to have control over the asynchronous data flow.

    App.vue
    <script setup>
    import Home from "./pages/Home.vue";
    </script>
    
    <template>
      <Suspense>
        <template #default>
          <Home />
        </template>
    
        <template #fallback>
          <div>Loading...</div>
        </template>
      </Suspense>
    </template>

    If using vue-router, a RouterView component will be necessary to view all pages within the project. If that's the case, instead of the above code in App.vue you can wrap the router component with Suspense.

    App.vue
    <script setup>
    import { RouterLink, RouterView } from "vue-router";
    </script>
    
    <template>
      <header>
        <div class="wrapper">
          <nav>
            <RouterLink to="/">Home</RouterLink>
            <RouterLink to="/about">About</RouterLink>
          </nav>
        </div>
      </header>
    
      <Suspense>
        <RouterView />
      </Suspense>
    </template>

    At this point, if you navigate to your Home page on Storyblok Visual Editor, you should see the components being rendered successfully.

    storyblok.com
    Storyblok editing capabilities

    Vue app integrated into Storyblok Visual Editor

    Real-time editing with Storyblok Bridge

    The power of Storyblok relies on its real-time editing experience. Play with changing the teaser headline or re-arranging the features and see the magic happen!

    Luckily, @storyblok/vue makes it very easy for you. In order to make this real-time editing experience possible, your components have to be connected with Storyblok and listen to changes by its Visual Editor. Let's take a closer look at how this is achieved:

    First, to link your Vue and Storyblok components together, @storyblok/vue automatically registers a v-editable directive. If you check Feature.vue, Grid.vue, and the rest under the components folder, you'll see you already have it there:

    components/Feature.vue
    <div v-editable="blok" class="py-2">
      <!-- ... -->
    </div>

    Second, @storyblok/vue loads Storyblok Bridge under the hood and exposes a one-liner useStoryblokBridge function that you can use to listen for Storyblok Visual Editor changes and update your component state, accordingly.

    In our example, we're using the short form syntax by importing useStoryblok in pages/Home.vue. This automatically loads Storyblok Bridge and enables it for you!

    In case you would like to work with useStoryblokApi and useStoryblokBridge separately, you can learn how to accomplish that in our docs on GitHub. Alternatively, you can check out the long form in our live demo.

    storyblok.com
    Storyblok editing capabilities

    Real-time editing experience enabled

    Wrapping Up

    Congrats on reaching this point! You have a Vue 3 app fully connected to Storyblok using its API, bringing a true real-time editing experience.

    Can it be even more simple? Yes. Check how to do it with Nuxt.js and find out how our @storyblok/nuxt module helps by doing some of these steps for you.

    LIVE DEMO:

    If you’re in a hurry, try yourself the live demo in Stackblitz!

    WHAT'S NEXT?:

    We (Storyblok) are huge fans of the Nuxt.js and the Vue.js universe. The Storyblok app is built in Vue.js and we are proud of it. As Nuxt.js is the all-in-one Vue.js meta-framework, we’ve built a Tech Hub to help you with the next steps on your Vue.js and Nuxt.js 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 marketing@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