Create a Preview Environment for Your Astro Website
Storyblok is the first headless CMS that works for developers & marketers alike.
In this tutorial, we are going to learn the essential steps to prepare our site for deployment. By default, Astro functions as a static site generator, generating all pages during the build process. However, previewing the site within the Storyblok Visual Editor requires a different approach than static.
This tutorial concludes the Ultimate Astro Tutorial series. To fully grasp the content, it's essential to go through all the preceding parts.
To address this challenge, we’re opting for a dual deployment strategy. One instance employs Astro SSR, ensuring smooth integration with the Storyblok Visual Editor for previews. The second version focuses on transforming the site into a static version optimized for production.
For this tutorial, we’ll be using Vercel (opens in a new window) for deployment. It's worth noting that this deployment method applies to any hosting provider supporting both SSR and SSG deployment for Astro. You can find a comprehensive list of such providers here (opens in a new window) .
If you’re in a hurry, have a look at our live demo on Vercel! Alternatively, you can explore or fork the code from the Astro Ultimate Tutorial GitHub Repository.
Preparing the codebase for a multi-strategy Deployment
First, let's set up an environment variable. Based on this variable, we'll decide whether to use SSR or SSG. Additionally, we currently always fetch the draft
version of the story from Storyblok in our code. We can make this dynamic, too.
STORYBLOK_TOKEN=xxxxx
STORYBLOK_IS_PREVIEW=yes
export default function isPreview() {
return import.meta.env.STORYBLOK_IS_PREVIEW === 'yes'
}
To handle SSR, we introduce a new variable, STORYBLOK_IS_PREVIEW. If it's set to yes
, we use Astro’s SSR mode and fetch the draft
data from Storyblok. For any other value, we do the opposite, rendering the site statically and fetching published
data.
Next, in our catch-all route, we face an issue. While getStaticPaths
works for SSG, it’s ignored in SSR, leading to errors. The problem arises from differences in fetching data between SSR and SSG.
To bridge this gap, we create a utility function that provides the same information for SSR as we get in SSG.
import { languages } from './langs'
export default function parseUrl(url) {
//converting the current url to an array based on '/'
let urlToArray = url?.split('/')
//Setting the fallback language to be english
let defaultLang = 'en'
//Checking if current url contains a known language
let isKnownLang = languages.some((l) => l === urlToArray?.[0])
//setting current language based on above
let currentLang = url && isKnownLang ? urlToArray[0] : defaultLang
// removing language from the url and only keeping the slug
let slug = url
? isKnownLang
? urlToArray?.slice(1)?.join('/') || undefined
: urlToArray?.join('/')
: undefined
//Same logic for generating the lang switch as we have in getStaticPaths
let langSwitch = {}
languages.forEach((lang) => {
langSwitch = {
...langSwitch,
[lang]: lang === 'en' ? `/${slug ?? ''}` : `/${lang}/${slug ?? ''}`,
}
})
//finally returning the same three variables we also get from getStaticPaths
return { language: currentLang, slug, langSwitch }
}
In the function above, we parse the full URL to extract both the language
and the slug
. As mentioned in our earlier sections, Storyblok considers language
as a parameter, not a part of the URL. Additionally, we'll generate the langSwitch
, similar to what we did in the getStaticPaths
function.
Additionally, let's relocate all the logic from the getStaticPaths
function in the [...slug].astro
file to a separate utility file. This will ensure our [...slug].astro
file remains clean and organized.
import { useStoryblokApi } from '@storyblok/astro'
import isPreview from './isPreview'
import { languages } from './langs'
export default async function generateStaticPaths() {
const storyblokApi = useStoryblokApi()
const links = await storyblokApi.getAll('cdn/links', {
version: isPreview() ? 'draft' : 'published',
})
let paths = []
links
.filter((link) => !link.is_folder)
.forEach((link: { slug: string }) => {
languages.forEach((language) => {
//This slug will be used for fetching data from storyblok
let slug = link.slug === 'home' ? undefined : link.slug
//This will be used for generating all the urls for astro
let full_url = language === 'en' ? slug : `${language}/${slug ?? ''}`
//This will let us change the url for diffrent versions
let langSwitch = {}
languages.forEach((lang) => {
langSwitch = {
...langSwitch,
[lang]: lang === 'en' ? `/${slug ?? ''}` : `/${lang}/${slug ?? ''}`,
}
})
paths.push({
props: { language, slug, langSwitch },
params: {
slug: full_url,
},
})
})
})
return paths
}
Now, let's modify our […slug].astro
file to handle both SSR and SSG. We use the utility functions we just created to make it happen.
---
import { useStoryblokApi } from '@storyblok/astro'
// @ts-ignore
import StoryblokComponent from '@storyblok/astro/StoryblokComponent.astro'
import BaseLayout from '../layouts/BaseLayout.astro'
import parseUrl from '../utils/parseUrl'
import isPreview from '../utils/isPreview'
import generateStaticPaths from '../utils/generateStaticPaths'
export async function getStaticPaths() {
//We have moved all the code to a generateStaticPaths() function.
return await generateStaticPaths()
}
const params = Astro.params
let props = isPreview() ? parseUrl(params?.slug) : Astro.props
const { slug, language, langSwitch } = props
const storyblokApi = useStoryblokApi()
const { data } = await storyblokApi.get(
`cdn/stories/${slug === undefined ? 'home' : slug}`,
{
version: isPreview() ? 'draft' : 'published',
resolve_relations: ['popular-articles.articles'],
language,
}
)
const story = data.story
---
<BaseLayout langSwitch={langSwitch} language={language}>
<StoryblokComponent language={language} blok={story.content} />
</BaseLayout>
Lastly, our AllArticle.astro
component, needs a similar adjustment.
---
import { storyblokEditable, useStoryblokApi } from '@storyblok/astro'
import ArticleCard from '../components/ArticleCard.astro'
import isPreview from '../utils/isPreview'
const { blok, language } = Astro.props
const storyblokApi = useStoryblokApi()
const { data } = await storyblokApi.get(`cdn/stories`, {
version: isPreview() ? 'draft' : 'published',
starts_with: 'blog/',
is_startpage: false,
language,
})
const articles = data.stories
---
...
Next, let's install the Vercel provider for Astro for deployment. You can follow this guide from Astro official docs to do this. (opens in a new window)
Now let's modify the astro.config.mjs
a bit more to make it fully dynamic so we can work from one codebase and deploy different instances as we need.
import { defineConfig } from 'astro/config';
import storyblok from '@storyblok/astro';
import { loadEnv } from 'vite';
import tailwind from '@astrojs/tailwind';
import basicSsl from '@vitejs/plugin-basic-ssl';
import vercel from "@astrojs/vercel/serverless";
const env = loadEnv('', process.cwd(), 'STORYBLOK');
// https://astro.build/config
export default defineConfig({
integrations: [storyblok({
accessToken: env.STORYBLOK_TOKEN,
+ bridge: env.STORYBLOK_IS_PREVIEW === 'yes',
components: {
page: 'storyblok/Page',
feature: 'storyblok/Feature',
grid: 'storyblok/Grid',
teaser: 'storyblok/Teaser',
hero: 'storyblok/Hero',
config: 'storyblok/Config',
'popular-articles': 'storyblok/PopularArticles',
'all-articles': 'storyblok/AllArticles',
article: 'storyblok/Article'
}
}), tailwind()],
+ output: env.STORYBLOK_IS_PREVIEW === 'yes' ? 'server' : 'static',
...(env.STORYBLOK_ENV === 'development' && {
vite: {
plugins: [basicSsl()],
server: {
https: true
}
}
}),
+ adapter: vercel()
});
Here is what we configured above:
- Enable Storyblok bridge only in the preview mode
- In preview mode, we are going to use
server
andstatic
for production. - Load the
basicSsl
only on local development (Optional)
Storyblok bridge is vital for the functionality of the Storyblok Visual Editor. To make it work, the Storyblok SDK adds extra information to HTML elements. This is ideal for the preview environment, but it's recommended to disable it for the production site. For more in-depth information, you can start your exploration here.
Deploying the preview version on Vercel
Begin by creating a new project in Vercel and importing the Git repository.
)
Create new project in Vercel
After completing this step, add your environment variables and proceed to deploy.
)
Add env variables in your vercel project
That's it! We have now successfully deployed the preview version of our Astro site. We will use this deployment URL in Storyblok to preview our website and leverage Storyblok's Visual Editor.
Deploying the production version on Vercel
For this step, follow the same process as mentioned above. Create a new project in Vercel and select the same Git repository. However, in the environment variables section, make sure to STORYBLOK_IS_PREVIEW
to NO
.
)
Add env variables for your Vercel project
This process will deploy our Astro site statically, allowing us to experience the lightning-fast speed that comes with a static site.
Configuring the rebuild trigger for the production deployment
Now that we have successfully deployed our production site statically, you might wonder what happens when you create a new story in Storyblok or make changes to your existing pages.
Not to worry - Storyblok provides Webhooks (opens in a new window) that we can utilize to listen for changes and trigger a rebuild on Vercel.
Let's navigate to the project settings in Vercel.
)
Project settings in Vercel
- Add a hook name
- Type the branch you want to build the trigger
- Click Create Hook.
After clicking on 'Create Hook,' it will provide us with a link as shown below. Let's copy this link and go to our Storyblok Space.
)
Create Deploy Hook in Project settings in Vercel
Navigate to the Webhook section in the settings of your Storyblok Space.
)
Create Webhook Hook in Storyblok Space
In this step, provide a name for the webhook and paste the copied URL from Vercel into the Endpoint URL field. Then, select all the events for which you want the build to be triggered
That's it! Your pipeline setup for the project, from preview to production, is now complete.
If you wish to explore further details about all the webhook events provided by Storyblok, you can delve deeper by clicking here.
Alternative to Webhooks?
Wondering about alternatives to webhooks? If you prefer not to trigger the build based on webhook events, you can configure our Tasks App to initiate the build manually.
)
Install Task Manager App in Storyblok
After installing the Task-Manager app in your space, you can create a new task and paste the link copied from Vercel into the Webhook field, following the line highlighted in the image below.
)
Create new task in Task Manager App in Storyblok
After saving the task, you have the flexibility to manually trigger the build whenever the need arises.
)
Trigger task in Task Manager App in Storyblok
Conclusion
Congratulations! You have successfully acquired the skills to develop a comprehensive Astro website using Storyblok. Throughout this tutorial, you have learned the essential steps to prepare our Astro site for deployment.
Resource | Link |
---|---|
Astro | https://astro.build/ |
The Storyblok Astro Ultimate Tutorial | https://www.storyblok.com/tp/the-storyblok-astro-ultimate-tutorial/ |
Storyblok SDK for Astro | https://github.com/storyblok/storyblok-astro |
Storyblok SDK Live Demo | https://stackblitz.com/edit/astro-sdk-demo |
Storyblok in the Astro Docs | https://docs.astro.build/en/guides/cms/storyblok/ |
Building Future-Proof High-Performance Websites With Astro Islands And Headless CMS | https://www.smashingmagazine.com/2023/02/building-future-proof-high-performance-websites-astro-islands-headless-cms-storyblok/ |
Storyblok APIs | https://www.storyblok.com/docs/api |