Storyblok
Search Storyblok's Documentation
  1. Integrate Eleventy with Storyblok

Integrate Eleventy with Storyblok

Use Storyblok to manage the content of your Eleventy website.

This guide has been tested with the following package versions:

  • @11ty/eleventy@3.0.0
  • @storyblok/js@3.4.0

Setup

Create a new Eleventy project in a few simple steps by following the Get Started page from its official documentation.

If you don’t already have a Storyblok space, create a starter space at app.storyblok.com, it already comes with initial content and component used in the first part of this guide.

Installation

In your terminal, cd into your Eleventy project and install the @storyblok/js and dotenv packages.

npm install @storyblok/js dotenv --save-dev

In the root of your project, create a .env file with the access token from your space.

STORYBLOK_DELIVERY_API_TOKEN="fqc3tdIuC8djNwEYl5cE5Att"

Learn how to get an access token for your Storyblok project.

Every Eleventy project requires a config file at root. Create one with the following content.

.eleventy.config.js
export default function eleventy() {

  return {
    dir: {
      input: 'src',
    },
  };
}

Create a storyblok.js file under the src/_utils directory and add this code:

src/_utils/storyblok.js
import { apiPlugin, storyblokInit } from '@storyblok/js';
import 'dotenv/config';

const { storyblokApi } = storyblokInit({
  accessToken: process.env.STORYBLOK_DELIVERY_API_TOKEN,
  apiOptions: {
    region: 'eu', // Choose the correct region from your Space.
  },
  use: [apiPlugin],
});

export default storyblokApi;

Ensure to set the correct region value depending on the server location of your Storyblok space. Learn more in the @storyblok/js package reference.

The Storyblok module will make features like fetching, components registration and bridge available for your project.

Fetch a single story

Create a home.js data file and use the client instance to fetch a story’s data.

src/_data/home.js
import storyblok from '../_utils/storyblok.js';

export default async function home() {
  const response = await storyblok.get("cdn/stories/home", {
    version: "draft", // or "published"
  });

  const { story } = response.data;
  const name = story.content.name;
  const body = story.content.body;
  
  return {
    name,
    body
  };
}

In a data file, we fetch a single story by its slug and return the content properties we want to use in our templates.

Learn more about how asynchronous data functions work in Eleventy on its documentation page.

Create a layout file as required by Eleventy to render a page.

src/_includes/layouts/base.liquid
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>{{ title }}</title>
</head>
<body>
  {{ content }}
</body>
</html>

Use the name of the data file on a content file as a namespace to access properties in the content area, or in the front matter through eleventyComputed to pass them to the layout.

src/index.md
---
layout: 'layouts/base.liquid'
eleventyComputed:
  title: '{{ home.name }}'
permalink: '/'
---

<main>
	{{ home.body }}
</main>

Eleventy can handle text based fields, and even Markdown with no configuration.

However, we need to deal with custom components within our space for a valid output.

Create and register blocks

Stories might contain a body or similar field which consists of an array with several blocks of custom types (e.g. Feature, Teaser, Grid) in it.

Create a function for each component that returns the resulting HTML for each.

src/_components/feature.js
function Feature(blok) {
  return (
    `<div class="feature">
       <span>${blok.name}</span>
     </div>`
  );
}

export default Feature;
src/_components/teaser.js
function Teaser(blok) {
  return (
    `<div class="teaser">
       <h1>${blok.headline}</h1>
     </div>`
  );
}

export default Teaser;
src/_components/grid.js
import Feature from './feature.js';
import Teaser from './teaser.js';

function Grid(blok) {
  return (
    `<div class="grid">
      ${blok.columns.map((nestedBlok) => {
        switch (nestedBlok.component) {
          case 'feature':
            return Feature(nestedBlok);
          case 'teaser':
            return Teaser(nestedBlok);
          default:
            throw new Error(`Grid could not resolve ${nestedBlok.component}.`);
        }
      }).join('')}
    </div>`
  );
}

export default Grid;

Iterate over the body array and resolve each component.

src/_data/home.js
import storyblok from '../_utils/storyblok.js';
import Feature from '../_components/feature.js'
import Teaser from '../_components/teaser.js'
import Grid from '../_components/grid.js'

export default async function home() {
  const response = await storyblok.get("cdn/stories/home", {
    version: "draft", // or "published"
  });

  const { story } = response.data;
  const name = story.content.name;
  const body = story.content.body
   .map(blok => {
     switch (blok.component) {
       case 'feature':
         return Feature(blok);
       case 'teaser':
         return Teaser(blok);
       case 'grid':
         return Grid(blok);
       default:
         throw new Error(`Could not resolve ${blok.component} block.`);
     }
   })
   .join('');
  
  return {
    name,
    body
  };
}

Similar to our Grid component, use the body to iterate over all blocks and render the corresponding one.

Run the server and visit the site in your browser.

npx @11ty/eleventy --serve