Skip to content

Integrate Eleventy with Storyblok

Use Storyblok to manage the content of your Eleventy website.

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

If you already have a Storyblok account, go to app.storyblok.com or log in with GitHub to continue.

Create a new blank space (opens in a new window) to follow the tutorial from scratch, or start from the core blueprint (opens in a new window).

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

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

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

Terminal window
STORYBLOK_DELIVERY_API_TOKEN="fqc3tdIuC8djNwEYl5cE5Att"

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;

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

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.

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.

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">
<h2>${blok.headline}</h2>
</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.

Terminal window
npx @11ty/eleventy --serve