The Complete Guide to Build a Full Blown Multilanguage Website with Next.js
Storyblok is the first headless CMS that works for developers & marketers alike.
This guide is for beginners and professionals who want to build a full-blown multilanguage website using Next.js and Storyblok. With this step-by-step guide, you will get a dynamic Next.js website running on Vercel, using the Storyblok API to manage multilanguage content.
If you are in a hurry you can download the whole source code of the project from GitHub https://github.com/storyblok/nextjs-multilanguage-website.
You can also take a look at the deployed Demo project: nextjs-storyblok-multilanguage-website.vercel.app/
Requirements
To continue with this tutorial, we don't expect you are an expert web developer, but you should understand a few basic concepts listed below this paragraph. We will guide you through most of the topics of this tutorial, but if you are beginning with Next.js & Storyblok, you should consider checking out our Add a headless CMS to Next.js in 5 minutes guide to begin.
Environment setup
If you haven't done so already, install Node.js and NPM on your machine. After that, run the following shell commands. This will clone the example repository for the multi-language blog.
Cloning the Storyblok Template
To get the correct space set up for the Github example repository, click the following link to duplicate our example multi-language space: https://app.storyblok.com/#!/build/95804. This will clone the space with the necessary content structure already set up.
The first step is to set our Preview Url to our development server. In the Storyblok app, go to Settings {1} > Visual Editor {2}, and set the Location (default environment) {3} to https://localhost:3010/
.
For this tutorial, we will set up our dev server with an HTTPS proxy, to use a secure connection with the application. We'll use port 3010, so the URL to access our website will end up being https://localhost:3010/
.
If you don't know how to setup an HTTPS proxy on macOS, you can read this guide.
You can also enter the URLs to enable and disable the preview mode: https://localhost:3010/api/preview?secret=MY_SECRET_TOKEN&slug= and https://localhost:3010/api/exit-preview?slug=. Read more about preview mode in our 5 minutes tutorial.
Connect Storyblok
In the next step, we need to retrieve the Preview token {3} from the Space Settings {1}, under Access Tokens {2}.
To activate the connection to Storyblok, open the pages/_app.js
file and enter the preview token you just retrieved, where it says accessToken
.
Let's open our Home Story now by clicking on Content {1} and then the Home Story {2}.
If your development server is running and your token is set correctly, you should see the URL on the top {1} and the Storyblok Bridge activated {2}. You should be able to click on the components {3} and directly edit them.
Understanding the Storyblok Bridge & Visual Editor
The Storyblok bridge is loaded and activated with the storyblokInit
function in the pages/_app.js file
, and via the Storyblok hook called useStoryblokState
used in the dynamic route pages/[[...slug]].js
. Read our Storyblok JS Bridge documentation to learn more about it.
We should use (or not) the useStoryblokState
hook to conditionally load the bridge, when we want it. In most cases, this would be used in combination with the preview mode, but can also be set to true to always load it.
Since our visual editor is already set up with these settings, we can take a look at the content in Storyblok.
Below you can see that once you cloned the example in Storyblok it will ship with sample content and components. We will use this content and the created components in our project. For your own project feel free to change, rename, or even delete those components and create your own. If you navigate to Block Library {1} in the main navigation you will see a list of components. These components, like the page component {2} for our pages and the post component {3} for our blog posts are ready to be used to in Storyblok and our project.
By default Storyblok ships with a component called Page. The Page component is a Content Type. This tells us that the Page component can be used to create new Stories. You would not be able to create new Stories from other components such as grid, feature, and teaser as they can only be used inside a field of the type blocks that lives inside a Content Type. That way you can create an almost infinite number of combinations with your components, with multiple levels of nesting.
Read more about components and the difference between Content Types and Bloks (nestable components) in an essential part of the developer guide.
We also have some content already set up. If you navigate to Content {1}, you will see the Home {2} and About {3} stories with the content type Page. You will also see a folder for the blog posts called blog {4}, which will have our blog posts.
To better understand the content structure, we strongly recommend reading the Structures of Content chapter of the developer guide.
Using Storyblok components in Next.js
Now that we already have components defined in Storyblok, let's take a look at the implementation of the components defined in the Next.js project. Open the components/Page.js
file:
By using storyblokEditable
with any component, we can make them clickable in Storyblok Visual Editor. If you want to control which components are clickable, you can add or remove the storyblokEditable
from the components.
Explanation of the blok Prop
You probably noticed the blok
prop in all of the components in the project. The prop is used to pass data down into each of the components. Keep in mind that you need to pass the data down to the nested components to render them.
Explanation of StoryblokComponent
You might have noticed a StoryblokComponent
in the code of some components (Page.js, Grid.js, etc.). We are using this feature to decide which component should be rendered on the screen depending on the component name it has in Storyblok. In the code of the pages/_app.js
file, you can see that all the possible components are getting imported and listed in the components
variable that is passed as a parameter to the storyblokInit
function. This will get the correct component rendered according to the value of the blok.component
property.
If you add a new component to your project, you will need to add it to that list.
You should save sensitive data and your API tokens,even through the Storyblok Content Delivery API token is read only, in .env and not directly in the pages/_app.js file as we did.
Using the Storyblok API client
To request content from Storyblok, we set up the connection between Next.js and Storyblok to get the data for the components, using storyblokInit
. In order to retrieve the data from the API, @storyblok/react
provides us the getStoryblokApi
function.
getStoryblokApi
gets the data from the Storyblok Content Delivery API, so we can use it in different files to request content.
Generating Pages from Storyblok Stories
We can automatically generate all the pages from our website with a single file: pages/[[..slug]].js
This file is using Next.js dynamic routes feature, with static generation. We will explain the parts of it in more detail.
getStaticPaths
Let's start by taking a look at the function that generates the HTML markup for the pages. In Next.js we can use the getStaticPaths function to generate static routes.
We use Storyblok's links
endpoint to request all link entries from Storyblok. This endpoint retrieves not only story links, but also folders. We check if the link is a folder and, if that's the case, we don't create the route. We use Next.js catch all functionality to create all the routes. We create a route for each locale
in Next.js. Read the Next.js tutorial Internationalized Routing to understand how that routing works. The basis for that is the locales
defined in next.config.js
:
These locales should match the languages defined in our Storyblok space under Settings {1}, Internationalization {2}.
getStaticProps
The next important function is the getStaticProps function. It's the function that generates static pages in Next.js. This is the place where we want to request data from Storyblok.
We join the slug
because in Catch-All routes the slug
needs to be an array. If slug
has no value, we're passing the slug home
. Then, we set the parameters that we'll pass to the Storyblok API. We can set the version
to request the published
or draft
content. We can resolve_relations
to bring the content related to linked stories. For example, when we select related blog posts in our blog. After that, we request the correct language from Storyblok depending on which locale
is active in Next.js.
Finally, we're requesting the correct story entry from Storyblok in the correct language. In the return statement, we need to pass our props
object.
Render function
The last missing part is our Page
function, which makes use of the useStoryblokState
hook. You can enable the StoryblokBridge and the Visual Editor on every page by default, or only inside of the preview mode. In most cases, you would only want to load the bridge if you're loading your page inside of the Visual Editor.
We're passing our language locale to our Layout.js
component and are automatically loading the right components, depending on the story content.
Post Component in Next
If we take a look at our BlogPost.js
component file, we can see it's really similar to the other components.
As you can see here, we used storyblok-rich-text-react-renderer
to render our Richtext content. To make this component work correctly in new projects we need to install the package from npm. You don't have to do this for the cloned project, since it's already installed.
Resolving Relations on Multi-Options fields
If you open the /blog/home
story, you will see the selected-posts
component. This component is set up with a multi-option field-type {1}, that allows referencing other story entries {2}. In this example, since we only want blog posts, we're limiting it to the content type post
. Open the following link with your preview token to see what it returns:
https://api.storyblok.com/v1/cdn/stories/blog/?version=draft&token=YOUR_PREVIEW_TOKEN
If you take a look into story.content.body[0].posts
, you will see, that it includes a list of uuids
. In order to actually get the full story objects, we have to resolve the relations first. Take a look at the following link with your preview token attached. This link is resolving the relations with the resolve_relations
parameter:
By using the resolve_relations option of the Storyblok API, we can get the full story objects of those related posts.
You can find multiple places where the relations are already resolved. Whenever we instantiate the Storyblok Bridge using the useStoryblokState
hook:
And also on the client call:
Adding Another Language
In Storyblok, we can implement internationalization mainly with two different approaches, it will depend on the use case. Read more about Internationalization in this guide. We are going to use "Field Level Translation" for this example.
If we want to add a new language to our space, all we need to do on the Storyblok side is go to the Settings {1} of our space and define a new language in the Internationalization {2} tab. Select a language {3} and click on the Add button {4}.
If we open any story in the Visual Editor, we will see the language dropdown in the header {1} and we can switch to another language by clicking on it {2}.
We need to define which fields we want to be able to translate. To see an example of how to do that, open the configuration for the field Headline {2} of the Teaser {1} component. Then click the Translatable check box {3}.
Change the language {1} to Spanish and you will see a Translate {2} toggle next to the translatable field. If you activate it, you are able to translate the value and you will see a real-time preview change. If the toggle stays false, the default language value will be used.
To load the correct language version, we will need to pass the locale
to our Storyblok Bridge, similar to the resolveRelations
. In the pages/[[...slug]].js
file, we pass the locale
to the useStoryblokState
.
Multi-language Navigation
To show the correct Navigation depending on the language, we can pass our Next.js locale
and locales
to our Layout.
The Layout file then passes the active locale
, as well as all active locales
, to the Navigation component.
Finally, we can build our multi-language navigation in the components/Navigation.js
file. We're resolving the current locale to get the correct language label. If you have a lot of navigation items, we recommend using a settings object in Storyblok to build your Navigation.
We're iterating over all the locales
that are set in Next.js and showing an entry to switch the locale
{1}. We're also highlighting the active locale with a black background.
If you want to add another language, you will need to add an entry to the next-config.js file, as well as the language Settings in your Storyblok space.
Deploying to Vercel
You have multiple options for the deployment of your website/application to go live or to preview the environment. One of the easiest ways is to use Vercel and deploy using the command line or their outstanding GitHub Integration.
First, create an account on Vercel and install the CLI application.
Deploy your website by running the vercel
command in your console.
Take a look at the deployed Demo project: nextjs-storyblok-multilanguage-website.vercel.app/
Resource | Link |
---|---|
Github repository | github.com/storyblok/nextjs-multilanguage-website |
The project deployed to Vercel | nextjs-storyblok-multilanguage-website.vercel.app |
Storyblok React SDK | storyblok/storyblok-react |
Vercel | Vercel |
Next.js docs | Next.js |
Storyblok App | Storyblok |
Next.js Technology Hub | Next.js Technology Hub |
Tailwind CSS with Next.js | How to install Tailwind CSS with Next |