Storyblok is one of the most popular new content management systems. Its visual editing and component-based workflow are intuitive for editors, while also being easy for developers to work with.
While its @storyblok/astro
integration with the Astro framework seems fairly straight-forward at first, getting the most out of both Storyblok's visual editor and Astro's static site generation is not as simple.
The Problem
Since Astro is (by default) a static site generator, which generates all pages at build time, editing content in Storyblok will not give your editors the instant feedback they might expect: Even if your Storyblok webhooks are set up correctly, it can take several minutes for the entire project to be rebuilt before the updated content appears. This makes for a very awkward and unintuitive editing experience.
One way to solve this problem is to use Astro in SSR mode. This uses a Nodejs server or serverless functions to render pages whenever they are requested, rather than at build time. This means pages will update as soon as editors change their content. Unfortunately this also means that you'll lose all benefits of a static site, such as the significantly faster page load times and lower operating costs.
The Solution
In order to get the best of both worlds, we'll want to set up two separate deployments: One that uses Astro's default SSG mode for your users to visit, and one SSR environment which is used as the preview site in Storyblok. This way, editors get instant feedback while editing content, but users will still get a fast, static site.
Assuming you followed the storyblok-astro
Getting Started guide, here is a step-by-step guide to enable this dual environment:
1. Install Astro SSR adapter of your choice
Astro supports several platforms for server-side rendering. For this guide I will use Vercel's serverless functions. Please refer to the Astro docs for other platforms.
npx astro add vercel
2. Conditionally enable SSR mode
This is where the magic happens: Based on an environment variable, we switch between Astro's server
or static
mode. I chose to call the variable PUBLIC_ENV
because Astro makes environment variables prefixed with PUBLIC_
available in client-side code, which might come in useful. We also conditionally load the SSR adapter since it's not needed in static mode.
// astro.config.{mjs,ts}
import vercel from '@astrojs/vercel/serverless';
// ...
export default defineConfig({
// ...
output: process.env.PUBLIC_ENV === 'preview' ? 'server' : 'static',
adapter: process.env.PUBLIC_ENV === 'preview' ? vercel() : undefined,
// ...
});
3. Conditionally enable the Storyblok bridge
The Storyblok bridge is responsible for reloading the page when there are changes to Storyblok content, as well as making individual components clickable in the Storyblok editor. Since we will never open the production static site in Storyblok, we can reduce the bundle size a little by disabling the bridge in production.
// astro.config.{mjs,ts}
export default defineConfig({
// ...
integrations: [
storyblok({
bridge: process.env.PUBLIC_ENV !== 'production',
// ...
}),
// ...
],
// ...
});
4. Create separate build scripts
In order to build the two different deployments, we'll create two build scripts which set the PUBLIC_ENV
variable to the respective value. For the sake of completeness, we'll also set the variable to development
when using the dev server.
// package.json
"scripts": {
"dev": "PUBLIC_ENV=development astro dev",
"build:production": "PUBLIC_ENV=production astro build",
"build:preview": "PUBLIC_ENV=preview astro build",
// ...
},
// ...
5. Deploy the two sites
Next, we'll create two separate deployments which use the different build scripts. In my case, I'll create two Vercel projects, connect both to the project's GitHub repo and set the Build Command under Settings > General > Build & Development Settings to npm run build:production
for the static production site and to npm run build:preview
for the SSR preview site. You should be able to do the same for Netlify, Cloudflare or similar providers.
6. Use webhooks to trigger production deploys
Now all that's left to do is to set up webhooks between Storyblok and your hosting provider so that the production site is rebuilt whenever content is published.
First, create a webhook in your production site's admin interface. For Vercel, I did this under Settings > Git > Deploy Hooks.
Then, copy the URL of the webhook you just created, and paste it in Storyblok under Settings > Webhooks > Story published & unpublished. Now your production site should be rebuilt whenever your editors publish or delete content!
Conclusion
This setup made Astro integrate with Storyblok much more nicely, and it is now one of my favorite framework-CMS pairings on the market. The awesome performance and developer experience of Astro static sites paired with the intuitive Storyblok visual editor is wonderful!
I hope this guide was helpful if you're building a site with these two technologies. Let me know if you have any questions or ideas for improvement!
Extras
There are a bunch of extra tips and tricks about Astro + Storyblok that I wanted to share, but since this guide is already getting long I decided to put them in a separate companion post, which you can find here: Astro + Storyblok: SSR Tips and Tricks. There I explain how to prevent drafts from showing up in your production site, disable search engine indexing of your preview site and more.
Top comments (10)
Thank you very much for this really great article. 👏
I'll add a bit more context to the
@storyblok/astro
readme to make it easier for new users to understand the need for two different deployments. 🙂Thank you for the kind words and for making the Astro integration! It's really been a joy to work with :)
Thank you, very happy to hear that. :-)
Thanks for this article Jonas! This solution actually came off the back of an issue that I highlighted towards the end of last year and Manuel managed to make it so!
We have some Astro production builds coming up and I was planning to do this exact setup myself, but now I don't need to since it's all here!
Thanks so much for this! You rock
Is it necessary to create two Vercel projects?
Not necessarily, I just found it to be the easiest and cleanest way to do it. One possible way to do it with a single Vercel project is to make a git branch for the SSR build, and use the Vercel branch preview URL for the Storyblok editor. But then you need to remember to keep that branch in sync with the main branch.
One of the steps that we can add is to use useStoryblokApi()
I found out that on new spaces the cdn/links endpoint is now paged by default.
so: 'const { data } = await storyblokApi('cdn/links');'
will now only get 25 results.
@jgierer12 have you tried this with dynamic routes at all eg [...slug].astro? We're having some problems trying to render them so wondered if you have attempted this.
I'm using it with the standard dynamic routes (
[slug].astro
without the rest parameter) and it works fine for me. I don't remember doing anything specific to make it work, it's pretty much just this with some minor changes. Maybe it could be an issue with the rest parameter?