@storyblok/astro
Astro integration for the Storyblok Headless CMS.
Kickstart a new project
Are you eager to dive into coding? Follow these steps to kickstart a new project with Storyblok and Astro, and get started in just a few minutes!
Ultimate Tutorial
Are you looking for a hands-on, step-by-step tutorial? The Astro Ultimate Tutorial has you covered! It provides comprehensive instructions on building a complete, multilingual website using Storyblok and Astro from start to finish.
Installation
Install @storyblok/astro
:
npm install @storyblok/astro
[!NOTE]
With pnpm, hoist Storyblok dependencies publicly with .npmrc
. For more information, check pnpm documentation on here.
Add the following code to astro.config.mjs
and replace the accessToken
with the preview API token of your Storyblok space.
import { defineConfig } from "astro/config";
import storyblok from "@storyblok/astro";
export default defineConfig({
integrations: [
storyblok({
accessToken: "<your-access-token>",
}),
],
});
[!WARNING]
This SDK uses the Fetch API under the hood. If your environment doesn't support it, you need to install a polyfill like isomorphic-fetch. More info on storyblok-js-client docs.
Options
When you initialize the integration, you can pass all @storyblok/js options.
storyblok({
accessToken: "<your-access-token>",
bridge: true,
apiOptions: {},
components: {},
componentsDir: "src",
enableFallbackComponent: false,
customFallbackComponent: "",
useCustomApi: false,
});
[!NOTE]
By default, the apiPlugin
from @storyblok/js
is loaded. If you want to use your own method to fetch data from Storyblok, you can disable this behavior by setting useCustomApi
to true
, resulting in an optimized final bundle.
Region parameter
Possible values:
eu
(default): For spaces created in the EUus
: For spaces created in the USca
: For spaces created in Canadaap
: For spaces created in Australiacn
: For spaces created in China
Full example for a space created in the US:
storyblok({
accessToken: "<your-access-token>",
apiOptions: {
region: "us",
},
});
[!WARNING]
For spaces created in the United States or China, the region
parameter must be specified.
Getting started
1. Creating and linking your components to the Storyblok Visual Editor
In order to link your Astro components to their equivalents you created in Storyblok:
First, you need to load them globally by specifying their name and their path in astro.config.mjs
:
components: {
page: "storyblok/Page",
feature: "storyblok/Feature",
grid: "storyblok/Grid",
teaser: "storyblok/Teaser",
},
[!NOTE]
The src
folder is automatically added to the beginning of the path, so in this example your Astro components should be located here:
src/storyblok/Page.astro
src/storyblok/Feature.astro
src/storyblok/Grid.astro
src/storyblok/Teaser.astro
You can choose any other folder in the src
directory for your Astro components.
[!NOTE]
If you prefer to use a different folder than src
, you can specify one using the componentsDir
option:
storyblok({
componentsDir: "app",
});
Now, your Storyblok components can be located anywhere in the app
folder, e.g. page: "storyblok/Page"
for app/storyblok/Page.astro
or page: "Page"
for app/Page.astro
.
For each component, use the storyblokEditable()
function on its root element, passing the blok
property that they receive:
---
import { storyblokEditable } from "@storyblok/astro";
const { blok } = Astro.props
---
<div {...storyblokEditable(blok)}>
<h2>{blok.headline}</h2>
</div>
Finally, you can use the provided <StoryblokComponent>
for nested components; it will automatically render them (if they have been registered globally beforehand):
---
import { storyblokEditable } from "@storyblok/astro";
import StoryblokComponent from "@storyblok/astro/StoryblokComponent.astro";
const { blok } = Astro.props
---
<main {...storyblokEditable(blok)}>
{blok.body?.map(blok => {return <StoryblokComponent blok="{blok}" />})}
</main>
[!NOTE]
The blok
is the actual blok data coming from Storblok's Content Delivery API.
Using fallback components
By default, @storyblok/astro
throws an error if a component is not implemented. Setting enableFallbackComponent
to true
bypasses that behavior, rendering a fallback component in the frontend instead. You can also use a custom fallback component by (for example) setting customFallbackComponent: "storyblok/MyCustomFallback"
.
Using partial hydration
If you want to use partial hydration with any of the frameworks supported by Astro, follow these steps:
- Install the official Astro integration for your desired framework
- Create an Astro component that serves as a wrapper and utilizes the most suitable client directive
- Create the actual component in Vue, Svelte, React or any other supported framework
For working examples, please refer to the Live Demo on Stackblitz.
2. Getting Storyblok Stories and using the Storyblok Bridge
Fetching one Story
Use the useStoryblokApi
function to have access to an instance of storyblok-js-client
:
---
import { useStoryblokApi } from "@storyblok/astro";
import StoryblokComponent from "@storyblok/astro/StoryblokComponent.astro";
const storyblokApi = useStoryblokApi();
const { data } = await storyblokApi.get("cdn/stories/home", {
version: "draft",
});
const story = data.story;
---
<StoryblokComponent blok="{story.content}" />
[!NOTE]
The available methods are described in the [storyblok-js-client] repository(https://github.com/storyblok/storyblok-js-client#method-storyblokget)
Dynamic Routing
In order to dynamically generate Astro pages based on the Stories in your Storyblok Space, you can use the Storyblok Links API and the Astro getStaticPaths()
function similar to this example:
---
import { useStoryblokApi } from "@storyblok/astro";
import StoryblokComponent from "@storyblok/astro/StoryblokComponent.astro";
export async function getStaticPaths() {
const storyblokApi = useStoryblokApi();
const { data } = await storyblokApi.getAll("cdn/links", {
version: "draft",
});
let links = data.links;
links = Object.values(links);
return links.map((link) => {
return {
params: { slug: link.slug },
};
});
}
const { slug } = Astro.params;
const storyblokApi = useStoryblokApi();
const { data } = await storyblokApi.get(`cdn/stories/${slug}`, {
version: "draft",
});
const story = data.story;
---
<StoryblokComponent blok="{story.content}" />
Using the Storyblok Bridge
The Storyblok Bridge is enabled by default. If you would like to disable it or enable it conditionally (e.g. depending on the environment) you can set the bridge
parameter to true
or false
in astro.config.mjs
:
[!NOTE]
Since Astro is not a reactive JavaScript framework and renders everything as HTML, the Storyblok Bridge will not provide real-time editing as you may know it from other frameworks. However, it automatically refreshes the site for you whenever you save or publish a story.
You can also provide a StoryblokBridgeConfigV2
configuration object to the bridge
parameter.
bridge: {
customParent?: string,
preventClicks?: boolean,
resolveRelations?: strings[],
resolveLinks?: string
}
customParent
is used to provide a custom URL for the Storyblok editor iframe.preventClicks
prevents the default behaviour of clicks when inside the Storyblok editor.resolveRelations
may be needed to resolve the same relations that are already resolved in the API requests via the resolve_relations
parameter.resolveLinks
may be needed to resolve link fields.
[!NOTE]
resolveRelations
and resolveLinks
will not have any effect in Astro, since the Storyblok Bridge is configured to reload the page. Thus, all the requests needed will be performed after the reload.
The provided options will be used when initializing the Storyblok Bridge. You can find more information about the Storyblok Bridge and its configuration options on the In Depth Storyblok Bridge guide.
If you want to deploy a dedicated preview environment with the Bridge enabled, allowing users of the Storyblok CMS to see their changes being reflected on the frontend directly without having to rebuild the static site, you can enable Server Side Rendering for that particular use case. More information can be found in the Astro Docs.
Rendering Rich Text
[!NOTE]
While @storyblok/astro provides basic richtext rendering capabilities, for advanced use cases, it is highly recommended to use storyblok-rich-text-astro-renderer.
You can easily render rich text by using either the renderRichText
function included in @storyblok/astro
.
Use renderRichText
, which only supports parsing and returning native HTML tags, if you are not embedding bloks
in your rich text. Then you can use the set:html
directive:
---
import { renderRichText } from "@storyblok/astro";
const { blok } = Astro.props
const renderedRichText = renderRichText(blok.text)
---
<div set:html="{renderedRichText}"></div>
You can also set a custom Schema and component resolver by passing the options as the second parameter of the renderRichText
function:
import { RichTextSchema, renderRichText } from "@storyblok/astro";
import cloneDeep from "clone-deep";
const mySchema = cloneDeep(RichTextSchema);
const { blok } = Astro.props;
const renderedRichText = renderRichText(blok.text, {
schema: mySchema,
resolver: (component, blok) => {
switch (component) {
case "my-custom-component":
return `<div class="my-component-class">${blok.text}</div>`;
break;
default:
return `Component ${component} not found`;
}
},
});
RichTextRenderer deprecated
Use the <RichTextRenderer />
component if you are embedding bloks
in your rich text:
[!IMPORTANT]
As of @storyblok/astro
v5, the <RichTextRenderer />
component has been removed. Use the renderRichText
function instead.
API
useStoryblokApi()
Returns the instance of the storyblok-js-client
.
Enabling Live Preview for Storyblok's Visual Editor
[!WARNING]
This feature is currently experimental and optional. You may encounters bugs or performance issues.
The Astro SDK now provides a live preview feature, designed to offer real-time editing capabilities for an enhanced user experience in Storyblok's Visual Editor.
[!NOTE]
To utilize the Astro Storyblok Live feature, Astro must be configured to run in SSR mode.
To activate the experimental live preview feature, follow these steps:
- Set
livePreview
to true
in your astro.config.mjs file. You can also add your bridge
configuration here, which will be applied to your bridge.
export default defineConfig({
integrations: [
storyblok({
accessToken: "OsvN..",
livePreview: true,
bridge: {
resolveRelations: ["featured.posts"],
}
}),
],
output: "server",
});
- Additionally, please use
getLiveStory
on your Astro pages for getting live story.
---
import { getLiveStory, useStoryblokApi, type ISbStoryData } from "@storyblok/astro";
import StoryblokComponent from "@storyblok/astro/StoryblokComponent.astro";
const { slug } = Astro.params;
let story: ISbStoryData = null;
const liveStory = await getLiveStory(Astro);
if (liveStory) {
story = liveStory;
} else {
const sbApi = useStoryblokApi();
const { data } = await sbApi.get(
`cdn/stories/${slug === undefined ? "home" : slug}`,
{
version: "draft",
resolve_relations: ["featured-articles.posts"],
}
);
story = data?.story;
}
---
<StoryblokComponent blok={story.content} />
- You can also listen to when the live preview is updated.
<script>
document.addEventListener("storyblok-live-preview-updated", () => {
});
</script>
Enabling Storyblok Loader
[!WARNING]
This feature is currently experimental and optional. You may encounters bugs or performance issues.
The Astro SDK now provides Storyblok Loader feature, It is designed to scale efficiently and handle large sites with thousands of pages highly performantly.
To activate the experimental Storyblok Loader feature, follow these steps:
- Set
contentLayer
to true
in your astro.config.mjs
file.
export default defineConfig({
integrations: [
storyblok({
accessToken: "OsvN..",
contentLayer: true,
}),
],
});
- Additionally, in your
src/content/config.ts
file define your collection.
import { storyblokLoader } from "@storyblok/astro";
import { defineCollection } from "astro:content";
const storyblokCollection = defineCollection({
loader: storyblokLoader({
accessToken: "fV...",
apiOptions: {
region: "us",
},
version: "draft",
}),
});
export const collections = {
storyblok: storyblokCollection,
};
- You can now use the storyblokCollection in your pages.
---
import StoryblokComponent from "@storyblok/astro/StoryblokComponent.astro";
import BaseLayout from "~/layouts/BaseLayout.astro";
import { getCollection } from "astro:content";
import { type ISbStoryData } from "@storyblok/astro";
export async function getStaticPaths() {
const stories = await getCollection("storyblok");
return stories.map(({ data }: { data: ISbStoryData }) => {
return {
params: { slug: data.full_slug },
props: { story: data },
};
});
}
interface Props {
story: ISbStoryData;
}
const { story } = Astro.props;
---
<BaseLayout>
<StoryblokComponent blok={story.content} />
</BaseLayout>
The Storyblok JavaScript SDK Ecosystem
Acknowledgements
Astro
We extend our deepest gratitude to the Astro team, especially Tony Sullivan, Matthew Philips, and Nate Moore, for their unwavering support in enhancing this integration. Your partnership is immensely valued.
Virtual Identity
Our heartfelt thanks go to Virtual Identity, one of our closest agency partners. The live preview feature owes its existence to the ingenuity and innovation of their team. Special recognition goes to their developer Mario Hamann for his pivotal live preview POC and continuous support.
Further Resources
Support
Contributing
Please see our contributing guidelines and our code of conduct.
This project uses semantic-release for generating new versions by using commit messages. We use the Angular Convention to name the commits.