Nuxt 3 + Storyblok for a Sushi Recipes Website

What happens when you combine the versatility of the NuxtJS Framework with a top-notch headless CMS solution like Storyblok?

The result is a sparkling website with a ridiculously easy content editing experience that deploys seamlessly on your cloud infrastructure of choice. Sounds amazing right?

In this article, we are going to learn how to combine both technologies using the Storyblok Nuxt module. The format of this post is going to be different than usual, we will go section by section checking the content of my last video along with the repository here.

# What the... is headless?

"Off with their heads!" said the Queen of Hearts! This phrase could come to your mind when thinking of headless, but it has a slightly different meaning (kinda) when we are talking about CMS solutions.

Do you remember those old, monolithic, self-hosted websites solutions where the BE (Back-end), the FE (Front-end), and the content were tightly coupled?

A Headless CMS is when the presentational layer or Front-end (a.k.a the "head") and it's entirely decoupled from the content repository and the BE system (a.k.a the "body")

With a content-first approach, users can author their content through an API and deliver it to any client app, could be a website, a mobile app, you name it.

Check the complete section here 00:38

# Storyblok

It's a leading technology in terms of headless CMS. What I personally like the most, is how easy is to change content, preview it and then deploy to production independently from tech stack you choose.

The developer and content experience are beautiful, something that is missing in most of the CMS out there.

For more info check here 01:22

# Content structures

Storyblok's way of creating content objects is quite cool. Have you ever played with Legos? They use bloks to create bigger pieces and each blok has its own set of properties (like color, width, height, specific form). This CMS uses bloks that represent components (hero, features section etc) to create bigger pieces (Stories), were ****each blok has their own set of fields (called Schema) holding your content.

There is also another important concept, Content Types, which is another type of component that represents templates for your stories. Examples of common Content Types are PostProductPage etc**.**

For more detailed explanation, check here 23:22

# Installation

Let's create a new Nuxt project:

npx nuxi init name-of-your-project

Then we install the module:

yarn add -D @storyblok/nuxt@next axios

Check complete process here 05:08.

# Setting up your Storyblok space

First you will need to create your user if you haven't already at Storyblok, then login to enter the dashboard and you will see a call to action next to the welcome hero. Click on it "Create a new space"

I will not cover the whole Dashboard explanation, if you are interested, you can check it on the video here 02:56.

Before we continue, please go to Settings on the left sidebar, then General and where it says Location (default environment) add your localhost url, which on Nuxt is http://localhost:3000

Now to link our module we will need a key, so click on API-Keys and copy your personal key from the list. ⚠️  Please be careful not to share or save this in a public place or in the repository.

# Configuring the Nuxt module

Add the following code to the modules section of nuxt.config.ts and replace the accessToken with API token from Storyblok space.

import { defineNuxtConfig } from "nuxt3";

export default defineNuxtConfig({
  modules: [
    ["@storyblok/nuxt", { accessToken: "process.env.STORYBLOK_API_TOKEN" }]
    // ...

Safe tip here: you can create and .envfile and add there your key and the API Url


Make sure you don't commit the file to your repository.

As a final step, run yarn dev on the project to serve the Nuxt application and if all goes smooth you should see the <NuxtWelcome /> component.

For the complete explanation refer to this section of the video 10:18.

# Creating our first page

Now is when the magic begins! Open Content on the sidebar and it will display a list of all the pages available. By default, there is a Home page already included. Do you notice that the Content Type is Page? We're going need this on a moment., but for now click on the Home item.

Do you remember when we add the default environment to http://localhost:3000? Here is why: Storyblok allows you to see the preview of your page directly for the CMS. On the right-hand sidebar you will see all the configuration for the current page, including the Bloks.

# Page component on Nuxt

On your Nuxt application, create a component under /components named ThePage , which is responsible to render all our Stories and Bloks. This is connected to

<script lang="ts" setup>
const props = defineProps({
  blok: {
    type: Object,
    required: true,

  <div v-editable="blok">
      v-for="blok in blok.body"

Check here for the complete process 27:49

# Nuxt home route

Since Nuxt has already a Router, just create an pages/index.vue to edit your Home page, this will correspond with the / path.

<script lang="ts" setup>

Since Storyblok uses a JSON data structure like this on the request

    "story": {
        "name": "Home",
        "slug": "home",
        "full_slug": "home",
        "id": 10718480,
        "uuid": "74a92c75-dbb6-4cb6-8c2f-b4582017c087",
        "content": {
            "_uid": "c0c5fd8d-6913-4ebf-bbed-a60d5e83dc1f",
            "component": "the-page",
            "body": [

We will use dynamic components from Vue3 a lot. Why? You see that story.content.component: 'the-page' on the response? We will pass exactly that field on our component tag <component :is="story.content.component" /> so Nuxt knows which components are associated to the story and be able to render it. Genius I know.

Then we need to fetch that JSON right? For that we're going to add some composable functions on our <script>.

import { useStoryApi, useStoryBridge } from '@storyblok/nuxt/composables'

const storyapi = useStoryApi()

const { data } = await storyapi.get('cdn/stories/home', {
  version: 'draft',

const state = reactive({
  story: data.story,

We use the useStoryApi to fetch directly to Storyblok cdn, and get the content of our Story (Home). For now let's fetch the content on draft instance (check content versions here)

Now we need to tell our Nuxt app to listen to changes made on the CMS dashboard and update the content, for that we are gonna use useStoryBridge on the mounted hook.

onMounted(() => {
  useStoryBridge(, event => {
    state.story = event

Check here 31:41


# Creating Bloks (components)

On the left sidebar go to Components, you will see a list of available ones (Storyblok gives you some default ones). Remember that we talk about the Page component which is a Content Type on the previous section? If you click on it you will be able to check it's Schema, which contains a field called body (check template of /components/ThePage.vue 😜 ) .

I will rename it to the-page to align with the name of the component on Nuxt, but by default is page.

Now click con New to create a Blok**,** give it a name (This name need to be kebab-case and the same as your Nuxt component to be able to render), I will call it the-hero 🦸🏻‍♀️ , make sure you check the Nestable checkbox before hitting Next.

On the tab Schema is where we are gonna define the properties to hold our content. since it's a Hero Component we will need:

  • Headline
  • Description
  • CTA's
  • Image

To add a new field just enter a key value and click the button + Add. By default it will create a simple Text Field, but if you click the field you can change its type (For more info check the Field Types docs).

# Hero component on Nuxt

Similar to our ThePage, we are gonna create another component called TheHero with the following code.

<script lang="ts" setup>
const props = defineProps({
  blok: {
    type: Object,
    required: true,
  <section class="hero w-full py-16">
    <h1 class="font-display text-shrimp-400 font-extrabold text-3xl mb-12">
       {{ blok.headline }}

All components need to have a prop called blok which is going to contain the actual component Schema from the JSON response done at page level.

To see the complete explanation + markup check the video at minute 34:30.

# Final result

Go back to your Home Story, on the right sidebar you will see a + Add block under the field label Body, click there and add your recently created component, once you do, click in it and add the content of each field you created before on the Schema.

If all went well (if not, you can always download the repo and that's it 😜) and you create your markup and style like in the video you should be able to see something like this:

Try to edit something and see how it updates automatically on the Preview on the left. That's what I call real-time editing 😱.

# Wrap up

Storyblok + Nuxt3 is a powerful combination to deliver a modern and flexible CMS experience to your clients and developers. Some people say it's impossible to have them both happy at the same time. Well, now you can 😉

I hope you enjoyed the article + video. See you around

Happy coding!

Don't miss out anything about Vue, your favourite framework.

Subscribe to receive all the articles we publish in a concise format, perfect for busy devs.

Related Articles

Generate and deploy the blog as a full static Nuxt site

Your site is ready for the world. Wait, where and how do I publish it? You only need 5 minutes to learn how to deploy a Nuxt.js site in Netlify.

Alex Jover Morales

Alex Jover Morales

Aug 25, 2020

Optimize SEO and Social Media Sharing in a Nuxt blog

Do you want your blog to reach an audience? Then you should have SEO in your mind. Learn two techniques you can easily apply to your Nuxt blog.

Alex Jover Morales

Alex Jover Morales

Aug 18, 2020

The new Provide and Inject in Vue 3

Getting stuck into the prop drilling? Learn how provide/inject can make your components more flexible and independent in this short tutorial.

Anthony Konstantinidis

Anthony Konstantinidis

Jul 18, 2022


VueDose is proudly supported by its sponsors. If you enjoy it, consider supporting it to ensure the project maintainability.

Learning Partner