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 3 version during the article, being the current and future of Vue.js.

    Following the Vue.js official installation guide, you have 2 ways to spin up a new project.

    If you want to go future-proof, use the new recommendation: 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@3

    If you’d rather go for a more mature option, you can use vue-cli as follows:

    npm install -g @vue/cli
    vue create PROJECT_NAME
    cd PROJECT_NAME
    vue upgrade --next

    At Storyblok we love the future, so for this article let's use the create-vue option. However, aside from the installation, the rest of the article works for both options.

    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:

    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 clicking + Create new space {1} and choosing the Create new space {2} option. Pick a name for it {3}.

    Storyblok editing capabilities
    1
    2
    3

    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 editing capabilities

    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 > General {1} and set the Location field to http://localhost:3000/ {2}:

    Storyblok editing capabilities
    1
    2

    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 Config tab {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-js-client and axios as its peer dependency:

    npm install storyblok-js-client axios

    They allow us to interact with the Storyblok Content Delivery API. You could also use the Fetch API or just plain axios, but using storyblok-js-client makes it more convenient because of caching, data formatting, and other interesting features.

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

    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 initialize the js client. You can use app.provide to make the client accessible in the components when using the Vue Composition API:

    src/main.js
    import StoryblokJS from "storyblok-js-client";
    import App from "./App.vue";
    
    const app = createApp(App);
    
    const storyApi = new StoryblokJS({
      accesstoken: "INSERT_YOUR_TOKEN"
    });
    
    app.provide("storyApi", storyApi);
    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>
    const props = defineProps({
      blok: Object
    });
    </script>
    components/Grid.vue
    <template>
      <div v-editable="blok" class="flex py-8 mb-6">
        <div :key="blok._uid" v-for="blok in blok.columns" class="flex-auto px-6">
          <component :blok="blok" :is="blok.component" />
        </div>
      </div>
    </template>
    
    <script setup>
    const props = defineProps({
      blok: Object
    });
    </script>
    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 setup>
    const props = 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>
    const props = 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);

    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 editing capabilities
    1
    2

    Get Draft JSON from Visual Editor

    From the code side, first create a pages/Home.vue component that uses the storyApi client to fetch the content:

    pages/Home.vue
    <script setup>
    import { inject, ref } from "vue";
    
    const storyApi = inject("storyApi");
    const { data } = await storyApi.get("cdn/stories/home", {
      version: "draft"
    });
    
    const story = ref(null);
    story.value = data.story;
    </script>
    
    <template>
      <Page
        v-if="story.content.component"
        :key="story.content._uid"
        :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>

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

    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, but we’re not making use of it yet. For that, you need to connect your components with Storyblok’s and listen to the Storyblok Visual Editor changes.

    Luckily, we have the Storyblok Bridge and @storyblok/vue to make it simple for you.

    The @storyblok/vue package is a Vue directive that connects your Vue and Storyblok components. Let’s install it:

    npm install @storyblok/vue

    And register it in your main.js:

    main.js
    import StoryblokVue from "@storyblok/vue";
    
    const app = createApp(App);
    app.use(StoryblokVue);
    // ...

    Now you would add v-editable to any component you want to be discoverable on Storyblok Visual Editor, but that’s already done. If you check Feature.vue, Grid.vue, and the rest under the components folder, you’ll see a v-editable="blok" directive in the root tag.

    On the other side, Storyblok Bridge allows you to listen to changes in Storyblok Visual Editor.

    To install it, add the following script to your index.html file by the end of <body>:

    index.html
    <script src="//app.storyblok.com/f/storyblok-v2-latest.js" type="text/javascript" async>
    </script>

    To make it simple, use a composable function to hold the Bridge logic so you can reuse it and keep your component clean. Create a composables folder with a useStoryBridge.js file in it:

    composables/useStoryBridge.js
    export default (id, cb, options = {}) => {
      const sbBridge = new window.StoryblokBridge(options);
    
      sbBridge.on(["input", "published", "change"], (event) => {
        if (event.action == "input" && event.story.id === id) cb(event.story);
        else location.reload();
      });
    };
    hint:

    to understand more in-depth how that code works, read the Storyblok Bridge guide

    Finally, use it in Home.vue to listen to update your story every time there is a change in the visual editor:

    pages/Home.vue
    <script setup>
    import useStoryBridge from "../composables/useStoryBridge";
    
    //...
    
    useStoryBridge(story.value.id, (evStory) => (story.value = evStory));
    </script>

    After following these steps, you should be able to go to the Storyblok Visual Editor and interact with the different “bloks” or components and change them real-time.

    Play with changing the teaser headline or re-arranging the features and see the magic happen!

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