Skip Navigation

Scott Spence

Getting set up with Storyblok and SvelteKit

12 min read
Hey! Thanks for stopping by! Just a word of warning, this post is almost 2 years old, . If there's technical information in here it's more than likely out of date.

I’ve been having a play around with Storyblok this evening and want to make some notes on getting set up with it.

With the moving target that is the public beta of SvelteKit there’s a few things that have changed recently which I want to document here.

If you’ve not used Svelte or SvelteKit before you can check out the awesome tutorial for Svelte over on svelte.dev/tutorial it’s got everything you need. There’s also learn.svelte.dev for SvelteKit which is under development at the time of writing.

If you want to follow along, minimum requirements will be a computer development setup with Node version 16+ installed and a text editor, I’ll be using VS Code.

Setup the SvelteKit project

I’ll get the SvelteKit project set up first with a few commands:

npm init svelte storyblok-and-sveltekit
cd storyblok-and-sveltekit

I’ll be using pnpm for installing dependencies, if you prefer npm or yarn you can use those instead. Just be aware if you’re copy pasting terminal commands you’ll need to change that.

I’ll be picking the following options from the SvelteKit CLI:

? Which Svelte app template? › - Use arrow-keys. Return to submit.
    SvelteKit demo app
❯   Skeleton project - Barebones scaffolding for your new SvelteKit app
? Add type checking with TypeScript? › - Use arrow-keys. Return to submit.
    Yes, using JavaScript with JSDoc comments
❯   Yes, using TypeScript syntax
    No
✔ Add ESLint for code linting? … Yes
✔ Add Prettier for code formatting? … Yes
✔ Add Playwright for browser testing? … Yes

These are the options I usually always pick when creating any new SvelteKit project. What I’ve picked will not impact the rest of this guide, especially ESLint, Prettier and Playwright.

If you decide you want to go without TypeScript support then remove the lang="ts" from the <script> tag in the coming examples.

If you decide to go with JavaScript with JSDoc comments then you’ll still need to annotate for types.

I’ll install dependencies then run the development server:

pnpm i
pnpm run dev

Going to http://localhost:5173 will display the SvelteKit skeleton project.

Setup Tailwind

Just before I go onto the Storyblok configuration, I’ll want to get some nice default styles into the project with Tailwind CSS. This is a one liner setup with the awesome Svelte Add project:

## --typography adds the Tailwind typography plugin
npx svelte-add@latest tailwindcss --typography

The script will configure Tailwind for use in the project and add in the typography plugin to the tailwind.config.cjs file.

All that’s needed to do after that is install the dependencies with pnpm i.

Get Storyblok setup

If you’re following along and you’ve not got a Storyblok account already then you’ll need to sign up for one, otherwise sign in.

From here, I’m greeted with a welcome screen and a button to ‘Create a new space’.

Clicking that I’ll be taken to the ‘Create a new space’ page with several options, the default ‘Create new space’ is good enough for this guide. I’ll give it a name (I’m calling mine ‘SvelteKit example’ very imaginative, I know 😊) then I’ll click the ‘Create space’ button.

storyblok-create-new-space

I’m then taken to the ‘Content’ section (in the left hand sidebar), from here there’s a list of content, as it was a new space (start from scratch) there’s only a home page content type shown in the main screen.

You’ll also notice in the sidebar toward the bottom there’s a ‘Switch to V2’ button.

storyblok-switch-to-v2-sidebar

I’ll be using the V2 version so clicking on that will change the screen and take me to the ‘Dashboard’ in the sidebar, I can then select ‘Content’ from the sidebar. On the main page I’ll select the ‘Home’ page content type.

That takes me to the visual editor displaying ✨ Welcome to the Visual Editor ✨ from this welcome section I can copy the ‘Access token’ needed for accessing the Storyblok API.

storyblok-welcome-to-the-visual-editor

There’s also a ‘Set up preview url’ section with an input field for the URL of my local dev server.

I’ll add in the suggested URL from the placeholder in the input field:

https://localhost:3000/

Yes! That is https!

Before clicking ‘Save and show’ I’ll need to get my SvelteKit project set up to enable the visual editor.

SvelteKit and Vite HTTPS server setup

Storyblok needs to run from a HTTPS server, so unlike normal local development (on HTTP) I’ll need to create a local HTTPS server. To do that I’ll install the @vitejs/plugin-basic-ssl Vite plugin and configure it. First up install the Vite plugin:

pnpm i -D @vitejs/plugin-basic-ssl

This is also a good time to set the server port as Vite will use the default port (5173) otherwise.

In the vite.config.js file I’ll add the following:

import { sveltekit } from '@sveltejs/kit/vite'
import basicSsl from '@vitejs/plugin-basic-ssl'

/** @type {import('vite').UserConfig} */
const config = {
  plugins: [sveltekit(), basicSsl()],
  server: {
    port: 3000,
    https: true,
  },
}

export default config

I can start the dev server now (pnpm run dev) and the terminal output will show the HTTPS server running.

Going to https://localhost:3000/ on Edge I get a warning.

storyblok-your-connection-isnt-private

From here I can click on the ‘Advanced’ button and select the ‘Continue to localhost (unsafe)’ link.

storyblok-your-connection-isnt-private-advanced

Now I have access to https://localhost:3000/ with a persistent warning from Edge to show that I’m using a connection without a certificate (https).

storyblok-not-secure

Over to the visual editor page on Storyblok I can click on the ‘Save and show’ button now.

The live preview will update showing a 404 page on here I need to click on the sliders for ‘Entry configuration’ in the header and set the ‘Real path’ as /.

storyblok-set-visual-editor-real-path

Clicking ‘Save & Close’ will now show the SvelteKit skeleton project!

storyblok-connect-to-local-host-success

Now I can configure Storyblok to work with my local dev server and my Storyblok project to show the page content type with it’s nested types on there.

Storyblok bridge with Svelte

If you’ve not used Storyblok before, this is where the magic happens. The Svelte Storyblok SDK (amongst other things) allows the live preview and editing of the components in Storyblok to be reflected on the project running on localhost.

Install the @storyblok/svelte package and the Storyblok peer dependency for axios, at the time of writing this was how I resolved this. There’s a mention of adding axios to optimiseDeps in the Add a Headless CMS to Svelte in 5 minutes guide but adding that to the vite.config.js file caused the same errors.

pnpm i -D @storyblok/svelte axios

Create SvelteKit files for use with Storyblok

I’ll create the files I’m going to need to get the connection between the local SvelteKit project and the Storyblok project set up.

Svelte Add already took care of creating the SvelteKit __layout.svelte file for me, so I’ll create some components to represent the various content types in Storyblok. I’ll need to create the following files:

  • Content type: page
  • Nested (Block): grid, feature, teaser

I’ll use the terminal to create the files and folders (directories) needed:

# create lib and components folders in src
mkdir -p src/lib/components
# Create the component files needed
touch src/lib/components/{feature.svelte,grid.svelte,page.svelte,teaser.svelte}

Environment variables

As I’ll want to be committing this to source control I don’t really want to add my access tokens. I can use the Vite way which is to add:

import.meta.env.VITE_VARIABLE_NAME

To the Svelte file and add the tokens to a .env file to access them, however, Vite will expose these on the client (the browser).

To keep them away from prying eyes I can use env-cmd which will allow me to access the environment variables without Vite.

I’ll install the env-cmd package for use shortly:

pnpm i -D env-cmd

I did a post a while back on SvelteKit .env secrets if you want to give that a read!

I’ll create a .env file in the root of the project and a env-vars.ts file in the src/lib folder.

touch .env
touch src/lib/env-vars.ts

In the .env file I can define however many environment variables I need for use locally. I’ve put an example for one for using draft and published tokens where the stage is published which is commented out:

# draft
STORYBLOK_ACCESS_TOKEN=my-local-access-token
STORYBLOK_STAGE=draft
# published
# STORYBLOK_ACCESS_TOKEN=my-production-access-token
# STORYBLOK_STAGE=published

I can create additional tokens in the ‘Settings’ > ‘Access tokens’ section on Storyblok.

storyblok-settings-access-tokens

Within the env-vars.ts file I can export the variables needed for the access token (STORYBLOK_ACCESS_TOKEN) and the stage (STORYBLOK_STAGE):

export const STORYBLOK_ACCESS_TOKEN = process.env['STORYBLOK_ACCESS_TOKEN']
export const STORYBLOK_STAGE = process.env['STORYBLOK_STAGE']

I will now be able to import these in the currently empty SvelteKit files.

Last thing to do here is to add env-cmd to the package.json scripts so it runs on the dev script:

"scripts": {
  "dev": "env-cmd vite dev",
  // rest of the scripts

If I wanted to create builds locally I’d also need to create a build:local script which uses env-cmd too:

"scripts": {
  "dev": "env-cmd vite dev",
  "build": "vite build",
  "build:local": "env-cmd vite build",
  "preview": "vite preview",
  // rest of the scripts

SvelteKit layout file

A layout file in SvelteKit is a way to persist elements across route changes, like a navbar and footer. This is also the place to initialise Storyblok for use across the project.

To load data before the page loads I’ll use the <script context="module"> script tags to initialise the Storyblok client.

I’ll add in the access token for the Storyblok project here and import the components needed for use in the storyblokInit function from the lib folder.

<script context="module">
  import Feature from '$lib/components/feature.svelte'
  import Grid from '$lib/components/grid.svelte'
  import Page from '$lib/components/page.svelte'
  import Teaser from '$lib/components/teaser.svelte'
  import { STORYBLOK_ACCESS_TOKEN } from '$lib/env-vars'
  import { apiPlugin, storyblokInit } from '@storyblok/svelte'
  import '../app.css'

  storyblokInit({
    accessToken: STORYBLOK_ACCESS_TOKEN,
    use: [apiPlugin],
    components: {
      feature: Feature,
      grid: Grid,
      page: Page,
      teaser: Teaser,
    },
  })
</script>

<main class="prose prose-xl">
  <slot />
</main>

Component files

Now I can get to adding code to the component files. Credit to Josefine Schaefer and her example code on GitHub.

The page component (page.svelte) is a root block content type and uses a Svelte action in storyblokEditable to use the prop being passed to it (export let blok).

Using a Svelte each expression I can loop through the blok.body and pass the blok to the StoryblokComponent as props.

I’m using the TypeScript any type because I was having issues using the StoryData type.

<script lang="ts">
  import {
    StoryblokComponent,
    storyblokEditable,
  } from '@storyblok/svelte'

  export let blok: any
</script>

<div use:storyblokEditable={blok} class="px-6">
  {#each blok.body as blok}
    <StoryblokComponent {blok} />
  {/each}
</div>

For the grid.svelte component, a similar story to the page component except I’m checking for a columns property and looping over that.

<script lang="ts">
  import {
    StoryblokComponent,
    storyblokEditable,
  } from '@storyblok/svelte'

  export let blok: any
</script>

<div use:storyblokEditable={blok} class="flex py-8 mb-6">
  {#each blok.columns as blok}
    <div class="flex-auto px-6">
      <StoryblokComponent {blok} />
    </div>
  {/each}
</div>

The feature.svelte file is nested in the grid component!

Let me break down my understanding of it, the blok prop being passed to the StoryblokComponent in grid.svelte is the prop being passed into this file (feature.svelte).

If I dump out the data being passed into the grid component, I get this:

columns: [
  {
    _uid: 'eb32fb87-721d-4834-8523-73ea81cbb6f7',
    name: 'Feature 1',
    component: 'feature',
    _editable: '<!--#storyblok#{"name": "feature"} -->',
  },
]

Then the data being passed to the feature component looks like this:

{
  _uid: 'eb32fb87-721d-4834-8523-73ea81cbb6f7',
  name: 'Feature 1',
  component: 'feature',
  _editable: '<!--#storyblok#{"name": "feature"} -->',
}

It’s the same data, with the individual columns being rendered out by feature.svelte as a child (or nested) component.

I’ll use the storyblokEditable action in the feature.svelte component passing in the blok prop and render out a div with the blok.name for the content.

<script lang="ts">
  import { storyblokEditable } from '@storyblok/svelte'

  export let blok: any
</script>

<div use:storyblokEditable={blok} class="py-2">
  <div class="text-lg">{blok.name}</div>
</div>

For teaser.svelte I’m going to use the same pattern as feature.svelte, but as I want to use it as the page heading, I’ll wrap the blok.name in a h1 tag.

<script lang="ts">
  import { storyblokEditable } from '@storyblok/svelte'

  export let blok: any
</script>

<div use:storyblokEditable={blok}>
  {blok.headline}
</div>

Svelte index (home) page

I’ve got all the components I need now for use in storyblokInit.

In the index page I’ll want to get the Storyblok home data for use in the page. Much like with the layout file I’ll want to get this data before the page loads so I’ll need a <script context="module"> block to call the results from the useStoryblokApi in a SvelteKit load function.

I’ll destructure the story out of the response from the API call and return that as props for the page to use.

With Svelte onMount, this is called once the page has loaded, this is when the useStoryblokBridge is called passing in the story prop from the context module.

Add additional field to Feature block

Let’s say now, if I wanted to add a bit more content to the feature component, like some body content explaining the feature. I’ll expand on that a little now.

What I’ll do to finish up here is add a new field to the feature block, I can get to my block library from navigating back from the visual editor page and select the ‘Block Library’ from the sidebar.

I can hover over the ‘feature’ block and click the ellipsis (...) and select ‘Edit’.

storyblok-edit-block-schema-menu

This brings up a context menu where I can edit the feature block and add additional fields to it, I’m going to give the field a name of body and click the ‘Add’ button.

storyblok-edit-block-schema-add-field

The newly created field will now show up in the list under the ‘name’ field.

storyblok-edit-block-schema-field-added

Clicking on that will take me to the edit field options where I can select the field type. I’m going to keep it as a text field, I could go into using the Storyblok RichTextResolver but I think I’ll leave that for another post.

storyblok-edit-block-schema-edit-field

Now, if I go back to the ‘Content’ section in the sidebar and select the ‘Home’ story, I can select the ‘Grid’ then ‘Feature’ I can see there’s a new ‘Body’ field I can start adding content to.

I’ll add in content to the three fields and save the content for each block as I go along.

To display the feature body content I’ll make a little adjustment to the feature.svelte file to include the blok.body content.

<script lang="ts">
  import { storyblokEditable } from '@storyblok/svelte'

  export let blok: any
</script>

<div use:storyblokEditable={blok} class="py-2">
  <div class="text-lg">{blok.name}</div>
  <div class="text-lg">{blok.body}</div>
</div>

Conclusion

There you have it! I’ve gone from scratch to a basic SvelteKit project to consume a Storyblok API.

I hope you found it useful and that you have everything you need to get started with Storyblok and SvelteKit.

I’ve only really scratched the surface of Storyblok here using the default story generated when creating a new space, but I’ve learned a lot in the process documenting this.

Resources

From searching around I found all these resources which helped me along the way:

https://www.storyblok.com/tp/add-a-headless-cms-to-svelte-in-5-minutes https://github.com/josefineschaefer/Storyblok-SvelteKit https://github.com/storyblok/storyblok-svelte https://www.storyblok.com/docs/Guides/nestable-blocks https://www.storyblok.com/docs/Guides/root-blocks

There's a reactions leaderboard you can check out too.

Copyright © 2017 - 2024 - All rights reserved Scott Spence