How to connect Eleventy and Storyblok

guaca

Estela Franco

Posted on February 10, 2024

How to connect Eleventy and Storyblok

This is the second post in a series of articles called “Eleventy and Storyblok” to learn how to use this combination to create a web project.

Before getting into the matter, I really recommend you to read my first article of this series if you haven’t yet. Take a moment to check it out and discover why Evleventy and Storyblok is my perfect combination and why you should start using it to take advantage of these two technologies. You can read i here.

So, in this second article, I explain to you how to connect Eleventy and Storyblok to start consuming content generated in this amazing Headless CMS.

Technical requirements

This article will show you step by step how to query data from Storyblok to use it in an Eleventy project. You'll need a computer with the following features:

  • Node.js. Eleventy requires Node.js 14. I've used v20.10.0 in the examples provided.
    • You can check if you have Node installed by running node --version in a terminal window.
    • If the command is not found or returns a number less than 14, you will need to download and install Node.js before proceeding to the next step.
  • A code editor. I use VSCode, but feel free to use your favorite.
  • A Storyblok account. You can create yours here.
  • Basic knowledge of HTML, CSS, JavaScript, Markdown, and the command line is helpful.

Once you have all the technical requirements in place, it's time to create the project.

Creating a project in Storyblok

First of all, let's create a basic Storyblok space for this project!

  • Log in to Storyblok and create a new space by clicking Add Space:

Add Space

  • Select the New Space option, enter a name for the space, and select the server location (EU by default):

New Space

You can review the structure and sample content details. By default, you'll find the following components:

  • A Home page in the Content section:

Content Section

When you access it, you will see the "Welcome to the Visual Editor" page by default, and the components of the page in the right sidebar:

Welcome to the Visual Editor

As you can see, the Home page has a body component with two block elements:

  • Teaser: this is a text component. The current value of this field is "Hello world!".

Hello World!

  • Grid: this is a block component, with 3 text elements in it (with the text "Feature 1", "Feature 2", and "Feature 3" text on them).

Grid component

We'll cover this later, but understanding the architecture of data is key to creating the right elements in your front-end development.

Creating a project in Eleventy

Eleventy does not require you to use any JavaScript framework or library. And you can have a basic project up and running in literally less than 1 minute:

  • Open a terminal
  • Create a folder for your new project
mkdir myproject
Enter fullscreen mode Exit fullscreen mode
  • Access the project
cd myproject
Enter fullscreen mode Exit fullscreen mode
  • Create an index.md file. The .md extension is for markdown.
echo '# Page header' > index.md
Enter fullscreen mode Exit fullscreen mode
  • Run Eleventy. This command compiles the index.md file (but .md is only one of many valid file extensions) into the output folder(_site by default), ready to be upload to your favorite hosting service.
npx @11ty/eleventy
Enter fullscreen mode Exit fullscreen mode

You should get something like this:

[11ty] Writing _site/index.html from ./index.md (liquid)
[11ty] Wrote 1 file in 0.03 seconds (v2.0.1)
Enter fullscreen mode Exit fullscreen mode
  • However, you may want to run Eleventy from a local development server while working on it. Run Eleventy locally with this code:
npx @11ty/eleventy --serve
Enter fullscreen mode Exit fullscreen mode

You will get something like this:

[11ty] Writing _site/index.html from ./index.md (liquid)
[11ty] Wrote 1 file in 0.04 seconds (v2.0.1)
[11ty] Watching…
[11ty] Server at http://localhost:8080/
Enter fullscreen mode Exit fullscreen mode
  • Open http://localhost:8080 in your browser and you will see your website.

Setting up and configuring the Eleventy project

So far, so simple, right? Your project should have the following architecture:

├── _site
│   └── index.html
└── index.md
Enter fullscreen mode Exit fullscreen mode

But let's add a few more things to prepare our project for our Storyblok integration.

We've created our first page, index.html, using one of the valid file extensions (.md). But this is just one of many options you can use for your templates:

  • HTML: *.html
  • Markdown: *.md
  • WebC: *.webc
  • JavaScript: *.11ty.js
  • Liquid: *.liquid
  • Nunjucks: *.njk
  • Handlebars: *.hbs
  • Mustache: *.mustache
  • EJS: *.ejs
  • Haml: *.haml
  • Pug: *.pug
  • Custom: *.*

I will use .md and .njk in this article, but feel free to use the template languages you feel more comfortable with.

There's plenty of options to use, combine and override the template language. You can find all the details in the documentation.

In addition to pages, you can use layouts to wrap other content. So let's open the project in VSCode and let's create a base layout to define the common HTML elements for all pages:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ "{{ title }}" }}</title>
  </head>
  <body>
    {{ "{{ content | safe }}"}}
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Save this file as base.njk in a new _includes/layouts folder. Your project now has the following structure:

├── _includes
│   └── layouts
│       └── base.njk
├── _site
│   └── index.html
└── index.md
Enter fullscreen mode Exit fullscreen mode

Now you can add the base.njk layout to your index.md page. To do this, add the following information to your existing file

+ ---
+ layout: layouts/base.njk
+ title: "This is my homepage"
+ ---

# Page header
Enter fullscreen mode Exit fullscreen mode

What have been done here? The content between the two --- marks is the front matter. This is a YAML block at the top of the file and is a standard convention used by many Static Site Generators (SSG). So you're probably familiar with it if you've used other SSGs in the past. The content in the front matter is key-value pairs that provide information on how to generate data for this page.

In this case, two key-value pairs have been added:

  • layout: This is the route of the desired layout to apply to the page.
  • title: This is the title of the page. As mentioned earlier in the base.njk file, this information will be used in the <title> tag.

Get the API token from Storyblok

An API token is necessary to follow the next steps. By default, you can see your preview token on the "Welcome to the Visual Editor" page. But you can find it (or create a new one) in Settings > Access Tokens as well.

Access Token on the Welcome to the visual editor page

Access token in Settings - Access Token section

How to query the Storyblok data from Eleventy

Create the GraphQL query

In this article, I will be using the GraphQL Content Delivery API. This is is a read-only endpoint and is optimized for fast content delivery.

Use the following endpoint to send GraphQL requests:

https://gapi.storyblok.com/v1/api
Enter fullscreen mode Exit fullscreen mode

Note that you need to append the region code if you are using different regions. For example:

https://gapi-us.storyblok.com/v1/api
Enter fullscreen mode Exit fullscreen mode

If you haven't used GraphQL schemas before, I have good news for you! Storyblok has a playground where you can explore the functionalities and create the right query. You can access it using this URL with your Access Token in it:

https://gapi-browser.storyblok.com/?token=insert-here-your-access-token
Enter fullscreen mode Exit fullscreen mode

In this case, we want to get the Home page data:

  • Teaser (Hello world!)
  • Grid
    • Feature 1
    • Feature 2
    • Feature 3

So, this will be our query:

{
  PageItem(id: "insert-your-home-id") {
    name
    content {
      body
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

You can find your Home id running the following query in the playground:

{
  PageItems {
    items {
      id
      name
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

And it will return the id and the name:

{
  "data": {
    "PageItems": {
      "items": [
        {
          "id": xxxxxxxxxx,
          "name": "Home"
        }
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Ok, now we know what data we want to get from Storyblok. But how to do it?

Data in Eleventy

Eleventy can use data from several different sources. However, when merging data, there is a priority order for data sources. From highest to lowest priority:

  • Computed data
  • Front matter data in a template
  • Template data files
  • Directory data files
  • Front matter data in layouts
  • Configuration API global data
  • Global data files

The two ways that work best for applying a headless CMS are Global Data files and Configuration API global data. In this article, I'm going to use the first one.

As you can imagine, this means some code is necessary to execute the API call. Let's create a folder called utils. Inside it, create a storyblok.js file with the following code:

module.exports = async function fetchAPI(query, { variables, preview } = {}) {
    const res = await fetch("https://gapi.storyblok.com/v1/api", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Token: "xxxxxxxxxxxxxxxxxxxxxxxx",
        Version: preview ? "draft" : "published",
      },
      body: JSON.stringify({
        query,
        variables,
      }),
    });

    const json = await res.json();
    if (json.errors) {
      console.error(json.errors);
      throw new Error("Failed to fetch API");
    }
    console.log(json.data)
    return json.data;
  }
Enter fullscreen mode Exit fullscreen mode

Hardcoding your API token into your code is not a good practice. So you can store it as an .env variable.

# .env file
STORYBLOK_TOKEN: xxxxxxxxxxxxxxxxxxxxxxxx
Enter fullscreen mode Exit fullscreen mode

However, you will need to install the dotenv package to access it.

npm install dotenv --save-dev
Enter fullscreen mode Exit fullscreen mode

You will see this step will generate some more files and folders. Don't worry, we'll manage this later. Now you can modify your storyblok.js file to use the .env variable:

+ require('dotenv').config()
module.exports = async function fetchAPI(query, { variables, preview } = {}) {
    const res = await fetch("https://gapi.storyblok.com/v1/api", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
-        Token: "xxxxxxxxxxxxxxxxxxxxxxxx",
+        Token: process.env.STORYBLOK_TOKEN,
        Version: preview ? "draft" : "published",
      },
      body: JSON.stringify({
        query,
        variables,
      }),
    });

    const json = await res.json();
    if (json.errors) {
      console.error(json.errors);
      throw new Error("Failed to fetch API");
    }
    console.log(json.data)
    return json.data;
  }
Enter fullscreen mode Exit fullscreen mode

At this point, let's create a _data folder, and a homepage.js file in it with the following code:

const fetchAPI = require("../utils/storyblok");

module.exports = async function getHomepage () {
  const data = await fetchAPI(`
  {
    PageItem(id: "insert-your-home-id") {
      name
      content {
        body
      }
    }
  }
  `);
  return data?.PageItem;
}
Enter fullscreen mode Exit fullscreen mode

The previous code will generate the homepage data from Storyblok's API, and it will be accessible from other pages and layouts.

So let's modify our index.md file to use it!

---
layout: layouts/base.njk
title: This is my homepage
---

- # Page header
+ {{ "{{ homepage.content.body[0].headline }}" }}
+ {{ "{{ homepage.content.body[1].columns[0].name }}" }}
+ {{ "{{ homepage.content.body[1].columns[1].name }}" }}
+ {{ "{{ homepage.content.body[1].columns[2].name }}" }}
Enter fullscreen mode Exit fullscreen mode

After following all these steps, you should see something similar to this on your local server:

Hello World. Feature 1. Feature 2. Feature 3.

Using the Visual Editor from Storyblok

As seen earlier, the Visual Editor uses a "Welcome to the Visual Editor" by default page. To see the page we've created using Storyblok's data we must set up the preview URL. Eleventy runs locally on http://localhost:8080. However, Storyblok requires an https connection to use the visual editor. There are several frameworks offering straightforward methods to use SSL in local development. Unfortunately, it's not that easy on Eleventy.

However, we can fis this issue with mkcert and local-ssl-proxy. Before following this process, ensure your local server is stopped. If it's still running, you can stop it by clicking Control + C.

  • Install mkcert (globally)
npm install -g mkcert
Enter fullscreen mode Exit fullscreen mode
  • Add mkcert to your local root CAs
mkcert -install
Enter fullscreen mode Exit fullscreen mode
  • Generate a certificate for your localhost on your project, signed by mkcert. This will generate two .pem files.
mkcert localhost
Enter fullscreen mode Exit fullscreen mode
  • Install local-ssl-proxy (globally)
npm install -g local-ssl-proxy

Enter fullscreen mode Exit fullscreen mode
  • Run Eleventy locally (and keep this terminal running it!). By default, it will run over the port 8080:
npx @11ty/eleventy --serve
Enter fullscreen mode Exit fullscreen mode
  • Open a new terminal and run the proxy SSL server through the port 3000 (and keep it running in this new terminal):
local-ssl-proxy --source 3000 --target 8080 --cert localhost.pem --key localhost-key.pem  
Enter fullscreen mode Exit fullscreen mode

After these steps, open https://localhost:3000 on your browser and you should be able to see the same content as on http://localhost:8080.

With these two local servers running, you can go to Storyblok and change the preview URL. You can set it up directly from the default "Welcome to the Visual Editor" page, in the "Set up preview URL" field. Or you can change the preview URL from Settings > Configuration > Visual Editor:

Set up preview URL

But you will probably be a bit disappointed after doing this because you'll get a 404 error after saving it. That's because Storyblok adds the content page path to the URL. So, by default, the visual editor will try to load https://localhost:3000/home. To change this, go to the page configuration and set / as the Real path:

Set / as the real path

And now you have it! You can see the content using the Visual Editor.

Home page inside the visual editor

Next steps

After following all these steps, you now know how to create content on Storyblok and consume it from Eleventy using the GraphQL API. However, you will notice that content management is not dynamic. If you change something in Storyblok, it will not be updated on the local server or in the visual editor.

That's the expected behavior because Eleventy executes the GraphQL query at build time. If we serve the page locally, this means that the query won't be updated unless we change a file in our project. Therefore, any changes we make won't be visible on Storyblok until we rebuild the project on our local server.

That's a bit annoying, isn't it? But don't worry. I will show you how to overcome all these inconveniences in my next article (part 3) of this series of Eleventy and Storyblok articles.

💖 💪 🙅 🚩
guaca
Estela Franco

Posted on February 10, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related