⚠️Draft Content

Storyblok Raises $80M Series C - Read News

What’s the True Total Price of Enterprise CMS? Find out here.

Skip to main content

E-Commerce Simplified: A Practical Guide to Building Your Store with Storyblok, Commercetools, and React.js

In this tutorial, we'll learn how to build a performant e-commerce store with Commercetools and React.js. You can explore the code for this demo on GitHub. You can also look at the live demo:storyblok-commercetools-react-storefront.vercel.app.

You can also jump into one of the specific chapters below

  1. Setup Storyblok Space
  2. Adding e-commerce integration
  3. Storefront in React.js
  4. Add Storyblok to the React.js starter
  5. Store deployment

Storyblok Space

First, sign up on Storyblok for a free account and create a new space. Next, add a name to your storefront and click Create space {3}

Create a space

Create a new Storyblok space

Once you've created a new space, you can check out our eCommerce storefront documentation to set up a storefront with Storyblok. Next, you will see some e-commerce components such as Product Feature {1}, Product Grid {2}, and Product Slider {3} components, as shown below.

E-commerce components
1
2
3

E-commerce components

Commercetools E-commerce Integration

Before navigating to our React application, we must first set up the Commercetools plugin from Storyblok. You can check out the commercetools guide on how to set up and retrieve your endpoint from commercetools.

Next, follow the integration guide on how to set it up. Once your integration works, you should be able to select a single anime product or even categories on Storyblok, as shown in the image below.

E-commerce plugin components

commercetools components


React.js Storefront

For this tutorial, I've created a template application showing how to connect Storyblok and the e-Commerce field type plugin. We'd work with React.js and the commercetools API as our eCommerce provider. To proceed, clone the repository as shown below

        
      git clone https://github.com/iamfortune/Storyblok-Commercetools-React-Storefront.git 
cd Storyblok-Commercetools-React-Storefront
npm i 
    

Connect your Storyblok Space to your React.js application

To connect to the Storyblok space you've created, navigate to the settings tab of your space, click on access tokens, and copy the preview token provided by Storyblok as shown below

Storyblok access tokens

Storyblok access tokens

Next, navigate to your visual editor tab and set up your Location (default environment), and your preview URLs with a localhost environment and preview URLs, as shown in the image below.

Preview URL

Preview URL

It's important to note that with Storyblok V2, you must set up your development server with an HTTPS proxy. We will use port 3010, so the URL to access the website will be https://localhost:3010/

IMPORTANT:

NOTE: Read this guide if you don’t know how to set up an HTTPS proxy on macOS.

As shown below, create a .env file and add your preview token to your .env file.

.env
        
      REACT_APP_STORYBLOK_API_KEY={YOUR_PREVIEW_TOKEN}
    

You can start your development server

        
      npm start 
    

Change the Real Path in Storyblok

When you start your server, navigate to your Storyblok space and click on the Entry configuration on the home page. Change the Real Path {1} to /. You should then view your home page as shown below

Entry configuration
1

Change the real path of your Home page in Storyblok

Create a Storefront on Storyblok

In this section, we'll create our storefront components in Storyblok. First, navigate to your Storyblok space and the Home Page. In our Next.js App.js, we see several components: a hero, a product grid, a product feature, and a product slider.

Our storefront guide shows how to set up the product and product slider components. Let's create the Hero and product grid components below.

Adding a Hero component

In the Home story, click Add Block and create a new block called Hero. The schema of the Hero component should look like this:

Creating a hero component

Creating a hero component

The schema for the Hero component can be found below:

        
      -- headline (type: Text) 
-- description (type: textarea)
-- image (type: asset)
    

Adding a Product Grid Component

Like our Hero component, click the Add block button again and create a new product grid block. Add two fields, categories {1}, and products {2} as shown below

product grid block
1
2

Adding fields to the product grid block

Navigate to the product field, and in the Plugin Options, select a custom type, sb-commercetools {1}, and for the Source, select Self {2}, and add your commercetools endpoints {3} and URLs as shown below:

Editing the product grid fields
1
2
3

Editing the product grid fields

Add Storyblok to React.js starter

In this section, we'd connect our Storyblok space to the React.js starter. First, let's install @storyblok-react package.

The @storyblok/react allows us to interact with content from Storyblok API and gives you a wrapper component that creates editable blocks in the Storyblok visual editor.

        
       npm install @storyblok/react
    

Create a Storyblok Config

In the src/App.js file, add the following code to import the storefront components and load the Storyblok client along with the API key stored in the .env file.

App.js
        
      // src/App.js

import { BrowserRouter, Routes, Route } from "react-router-dom";
//import for Storyblok dependencies
import { storyblokInit, apiPlugin } from "@storyblok/react";
//import for components
import Homepage from "./pages/Home";
import ProductDetails from "./pages/ProductDetails.jsx";
import Page from "./components/Page";
import ProductGrid from "./components/ProductGrid";
import ProductFeature from "./components/ProductFeature";
import ProductSlider from "./components/ProductSlider";
import Hero from "./components/Hero";

// Initialize Storyblok
storyblokInit({
  accessToken: process.env.REACT_APP_STORYBLOK_API_KEY, // Your public access token
  use: [apiPlugin],
  components: {
    hero: Hero,
    page: Page,
    "product-grid": ProductGrid,
    "product-feature": ProductFeature,
    "product-slider": ProductSlider,
  },
});

// Creating routes for the application's pages
const App = () => {
  return (
    <BrowserRouter>
      <Routes>
        {/* Home page */}
        <Route path="/" element={<Homepage />} />
        {/* Product details page */}
        <Route path="/product/:id" element={<ProductDetails />} />
      </Routes>
    </BrowserRouter>
  );
};

export default App;
    

Creating Storefront Pages

In this section, we will create pages for our storefront application. To do this, navigate to your src folder, and inside it, create the folder, pages. In our Pages folder, create two files, Home.jsx and ProductDetails.jsx

Below, we will use the Storyblok API to fetch stories 

This section above will create two defined routes for a Homepage and a ProductDetails page. To do this, navigate to your src folder; inside it, create a folder named Pages, and in it, two files: Home.jsx and ProductDetails.jsx. Copy and paste the following to the respective file into your Home.jsx:

Home.jsx
        
      // src/pages/Home.jsx

import { useEffect, useState } from "react";
import styled from "styled-components";
import {
    useStoryblokState,
    getStoryblokApi,
    StoryblokComponent,
} from "@storyblok/react";
import { Link } from "react-router-dom";

const Homepage = () => {
    const storyblokApi = getStoryblokApi();
    const [story, setStory] = useState();
    const home = useStoryblokState(story);
    const fetchHomeStory = async () => {
        try {
            const { data } = await storyblokApi.get("cdn/stories/home", {
                version: "draft",
            });
            setStory(data?.story);
        } catch (error) {
            console.log(error);
        }
    };
    useEffect(() => {
        fetchHomeStory();
    }, []);

    return (
        <StyledSection>
            <Link to="/">
                <h3 className="text-black text-3xl">Anime Store</h3>
            </Link>
            <main className="!mt-20">
                {home?.content && <StoryblokComponent blok={home.content} />}
            </main>
        </StyledSection>
    );
};
const StyledSection = styled.section`
    max-width: 90%;
    margin: 2rem auto;
    overflow: hidden;
`;

export default Homepage;
    

In the code above, the story home is fetched from Storyblok, and the story response object is passed to the story state. If the state contains the content object, the defined Storyblok component will be displayed.

Next, we will create the ProductDetails page, which will contain the product name, an image of the product, a description, and the product price for a single product. Add the code block below to your ProductDetails.jsx file

ProductDetails.jsx
        
      // src/pages/ProductDetails.jsx

import { useEffect, useState } from "react";
import { Link, useParams } from "react-router-dom";
import styled from "styled-components";
import {
  useStoryblokState,
  getStoryblokApi,
  storyblokEditable,
} from "@storyblok/react";
const ProductDetails = () => {
  const { id } = useParams();
  const storyblokApi = getStoryblokApi();
  const [story, setStory] = useState();
  const product = useStoryblokState(story);
  const fetchHomeStory = async () => {
    try {
      const { data } = await storyblokApi.get(`cdn/stories/product/${id}`, {
        version: "draft",
      });
      setStory(data?.story);
    } catch (error) {
      console.log(error);
    }
  };
  useEffect(() => {
    if (id) {
      fetchHomeStory();
    }
  }, [id]);
  return (
    <StyledSection>
      <Link to="/">
        <h3 className="text-black text-3xl">Go Back</h3>
      </Link>
      <main
        className="!mt-20 flex items-center justify-center"
        {...storyblokEditable(product?.content)}
      >
        <img
          src={product?.content?.images[0]?.filename}
          alt={product?.content?.images[0]?.alt}
          style={{ width: 240, height: 320 }}
          className="object-cover"
        />
        <div className="ml-10">
          <h2 className="text-2xl font-bold mb-1">{product?.content?.name}</h2>
          <p>{product?.content?.description}</p>
          <h2 className="text-2xl font-bold mt-10 mb-4">
            ${product?.content?.price}
          </h2>
          <button>Order now</button>
        </div>
      </main>
    </StyledSection>
  );
};
const StyledSection = styled.section`
  max-width: 90%;
  margin: 2rem auto;
  overflow: hidden;
  & button {
    display: block;
    background: #03b3b0;
    color: #fff;
    height: 45px;
    padding: 0 1.5rem;
    border-radius: 6px;
  }
`;

export default ProductDetails;
    

In the code above, we created a ProductDetails component to fetch a product and display its details for a single product using the id. The fetchHomeStory function fetches the product story from the Storyblok API and updates the state variable with the fetched data.

It then displays the product image, name, description, price, and a button to order the product.

Adapting the Components to Storyblok

Earlier on, we created a set of components for the e-commerce storefront on Storyblok. To access the Storyblok content, we will create corresponding components in the React.js application.

Hero.jsx

In this component, we will add the blok prop and use the corresponding Storyblok content (if it exists). The blok prop will contain the application's headline, description, and hero image.

Hero.jsx
        
      // src/components/Hero.jsx

import { storyblokEditable } from "@storyblok/react";
import styled from "styled-components";
const Hero = ({ blok }) => {
  return (
    <StyledDiv className="flex items-center" {...storyblokEditable(blok)}>
      <div style={{ minWidth: 300 }}>
        <h2 className="mb-4">{blok?.headline}</h2>
        <p>{blok?.description}</p>
      </div>
      <img
        {...storyblokEditable(blok)}
        src={blok?.image?.filename}
        alt={blok?.image?.name}
      />
    </StyledDiv>
  );
};
const StyledDiv = styled.div`
  & h2 {
    font-size: 32px;
    line-height: 27px;
  }
  & p {
    line-height: 26px;
  }
  & img {
    dispay: block;
    width: 100%;
  }
`;

export default Hero;
    
Hero component

Hero component on Storyblok

ProductFeature.jsx

This component will utilize the blok property, and contain the title and image of the featured product in the e-commerce application.

ProductFeature.jsx
        
      // src/components/ProductFeature.jsx

import { storyblokEditable } from "@storyblok/react";
const ProductFeature = ({ blok }) => {
    return (
        <div className="mt-40" {...storyblokEditable(blok)}>
            <h2 className="text-8xl text-center mb-10">{blok.headline}</h2>
            <div
                style={{
                    background: `url(${blok?.product?.items[0]?.image}) no-repeat center center/cover`,
                    height: 600,
                    width: "100%",
                    borderRadius: 12
                }}
            />
            <h3 className="text-xl text-right">
                {blok?.product?.items[0]?.description}
            </h3>
        </div>
    );
};

export default ProductFeature;
    

The results can be shown in the visual editor as seen below:

Product feature blok

Product feature component

ProductGrid.jsx

Here the grid component will display all products from the blok prop. The content to be displayed are the product title, image, and description.

ProductGrid.jsx
        
      // src/components/ProductGrid.jsx

import { storyblokEditable } from "@storyblok/react";
import { Link } from "react-router-dom";
import styled from "styled-components";
const ProductGrid = ({ blok }) => {
    const getSlug = (name) => {
        return name.toLowerCase().trim().replaceAll(" ", "-");
    };
    return (
        <StyledDiv {...storyblokEditable(blok)}>
            <h1 className="text-5xl mb-6">Products</h1>
            <div className="flex flex-wrap">
                {blok?.products?.items.map((item) => (
                    <div key={item?.name} className="img-wrapper mr-8 mb-8">
                        <Link to={`/product/${getSlug(item?.name)}`}>
                            <img key={item?.id} src={item?.image} alt={item?.name} />
                        </Link>
                        <h4 className="font-bold mt-1 mb-1">{item?.name}</h4>
                        <p>{item?.description}</p>
                    </div>
                ))}
            </div>
        </StyledDiv>
    );
};
const StyledDiv = styled.div`
    margin-top: 10rem;
    & .img-wrapper {
        width: 320px;
        & img {
            width: 100%;
            height: 240px;
            object-fit: cover;
            border-radius: 8px;
        }
    }
`;

export default ProductGrid;
    

You can see the application as shown in the image below:

Product Grid component

Product Grid component

ProductSlider.jsx and Page.jsx

To add the following component to the React application, first, we'd create the product slider component by creating a new file ProductSider.jsx and inside it, we'd add the following lines of code below:

ProductSlider.jsx
        
      // src/components/ProductSlider.jsx

import { storyblokEditable } from "@storyblok/react";
const ProductSlider = ({ blok }) => {
    return <h2 {...storyblokEditable(blok)}>{blok.headline}</h2>;
};

export default ProductSlider;
    

Next, our Page.jsx component will render all the data for our application page. To do this, add the following lines of code to your Page.jsx as shown below

Page.jsx
        
      // src/components/Page.jsx

import { storyblokEditable, StoryblokComponent } from "@storyblok/react";
const Page = ({ blok }) => {
    return (
        <main {...storyblokEditable(blok)}>
            {blok.body.map((nestedBlok) => {
                return <StoryblokComponent blok={nestedBlok} key={nestedBlok._uid} />;
            })}
        </main>
    );
};

export default Page;
    

Retrieving data from Commercetools in a React app

This section will show you how to fetch products from Commercetools using the product ID. First, in your pages folder, create a new file called FetchProducts.jsx

Next, install commercetools SDK for clients, middleware-http and middleware auth using the command below:

        
      npm install @commercetools/sdk-client @commercetools/sdk-middleware-auth @commercetools/sdk-middleware-http
    

The packages above allow us to create a middleware and connect to our commercetools project in our React application. 

Next, initialize the middleware in our file using the Commercetools middleware package. We'd also initialize a client middleware as shown below

FetchProducts.jsx
        
      import { useEffect, useState } from "react";
import styled from "styled-components";
import { Link } from "react-router-dom";
import { createClient } from "@commercetools/sdk-client";
import { createAuthMiddlewareForClientCredentialsFlow } from "@commercetools/sdk-middleware-auth";
import { createHttpMiddleware } from "@commercetools/sdk-middleware-http";

const authMiddleware = createAuthMiddlewareForClientCredentialsFlow({
    host: process.env.REACT_APP_CT_AUTH_URL,
    projectKey: process.env.REACT_APP_CT_PROJECT_KEY,
    credentials: {
        clientId: process.env.REACT_APP_CT_CLIENT_ID,
        clientSecret: process.env.REACT_APP_CT_CLIENT_SECRET,
    },
    scopes: [process.env.REACT_APP_CT_PRODUCT_SCOPES],
});

const httpMiddleware = createHttpMiddleware({
    host: "https://api.europe-west1.gcp.commercetools.com",
});

const client = createClient({
    middlewares: [authMiddleware, httpMiddleware],
});

const FetchProducts = () => {
    const [products, setProducts] = useState([]);
    useEffect(() => {
        client
            .execute({
                uri: `/${process.env.REACT_APP_CT_PROJECT_KEY}/products`,
                method: "GET",
            })
            .then((response) => setProducts(response.body.results))
            .catch((error) => console.error(error));
    }, []);
    return (
        <StyledSection>
            <Link to="/">
                <h3 className="text-black text-3xl">Anime Store</h3>
            </Link>
            <main className="!mt-20">
                <pre>{JSON.stringify(products, null, 2)}</pre>
            </main>
        </StyledSection>
    );
};

const StyledSection = styled.section`
    max-width: 90%;
    margin: 2rem auto;
    overflow: hidden;
`;

export default FetchProducts;
    

In the code above, we initialized a client object to communicate with commercetoools for fetching products using their IDs

The createAuthMiddlewareForClientCredentialsFlow function creates a middleware to handle authentication for our client credentials flow with our commercetools environment variables.

The createHttpMiddleware function creates an HTTP middleware to handle requests in our React application, the host parameter points to our commercetools project endpoint.

Our createClient function creates a client instance to execute our requests to our commercetools endpoint.

In the fetchProducts function, we use React useState to initialize the products state to an empty array while we use the setProducts function to update our state when products are fetched. 

We then fetch products from the commercetools endpoint /product_key/products.

IMPORTANT:

You can learn how to retrieve data/products from commercetools from their documentation here.


Deployment

The created e-commerce storefront can be deployed on a hosting platform like Vercel or Netlify. For this demo, we will use Vercel for deployment. We must set up the CLI tool and set up a user account.

To install the Vercel tool, enter the following in the CLI environment:

        
      npm i -g vercel 
    

To deploy the application to the production environment, enter the following command.

        
      vercel deploy --prod
    

Conclusion 

Using a headless approach to your eCommerce needs can improve your user experience. Storyblok lets you access a visual editor for your content editors and developers while storing your products on commercetools. The two combined can double your performance and reduce customer load times. You can learn more about Storyblok and eCommerce with the resources below



Author

Fortune Ikechi

Fortune Ikechi

Fortune Ikechi is a Software Engineer proficient with MERN stack and lover of Golang. Fortune is passionate about community building and open source.