DEV Community

Cover image for How to Build an ecommerce site using Medusa, Next.js and Stackbit
Mark Munyaka
Mark Munyaka

Posted on

How to Build an ecommerce site using Medusa, Next.js and Stackbit

Introduction

Building modern ecommerce storefronts can be a challenge. There is a need for both technical prowess and ease of content management of the site. Developers want to build performant and secure sites, while marketers want to push sales through visually appealing storefronts. Usually, tradeoffs have to be made on either side of the equation. This is where Stackbit comes in.

Ecommerce

In this step-by-step tutorial, you will learn how to use Stackbit and Medusa to build a custom storefront for an ecommerce website. You will learn how to use the visual building experience that Stackbit’s site builder provides to edit and build the site. Then see how the site integrates with Medusa’s REST API.

What is Stackbit?

Stackbit is a visual experience platform that helps developers build a custom site-building experience for content managers of the site. It provides a mechanism for connecting content sources like a CMS with the frontend of a site all through a visual builder.

Here are some of the benefits of using Stackbit:

  1. Flexible development and editorial workflow: Developers can bring their own tech stack and use their preferred component libraries and UI kits while content editors can make visual changes using a no-code approach with minimal annotations.
  2. Real-time updates: Changes made in Stackbit or in the content source are updated instantly, providing a seamless and efficient workflow for both developers and content editors.
  3. Cloud-based visual editor: Stackbit provides a cloud-based visual editor that picks up code changes automatically and launches an isolated container to render the site's preview.
  4. Own your content: Stackbit does not store your content and reads and writes content directly from your API CMS or files in Git, allowing you to fully own and control your content.
  5. Stackbit is fully composable: Stackbit works with any Node-based stack, including popular tools like Next.js, React & Tailwind, allowing for seamless integration with existing workflows and tools.

If you want to build an ecommerce storefront using Stackbit you will need a composable commerce engine to power up the tech stack. This is where Medusa comes in.

What is Medusa?

Medusa is an open source composable commerce backend full of features to help build your ecommerce website. It has a headless server that exposes the REST APIs for your ecommerce store, an admin panel to manage your ecommerce site, and a storefront. It is fully customizable and extensible. You can build webshops, subscription services, digital products stores, ticketing systems, and any other ecommerce solutions.

Here are some of the benefits of Medusa:

  1. Medusa is fully open source. No paid features or proprietary backends. No limit on the customizations, features, and integrations you can put.
  2. Medusa has a wide range of ecommerce features including payment providers, cart, checkout, gift codes, multi-currency support, PIM, and many more.
  3. Medusa is composable. You can set up your store to the specifications that best suit your business needs with seamless third-party integrations.
  4. Medusa is headless. You can use any frontend framework to interact with it.
  5. Medusa offers community support despite being free and open source. Through their Discord channel and GitHub discussions, you will have enough help to get past any bottleneck.

Resources

Here is a demo of the website you will build.

Ecommerce website demo

Prerequisites

Medusa Server Setup

Medusa provides the ecommerce backend for the storefront you will create. Let’s get the server up and running.

Install Medusa CLI tool

Install the Medusa CLI tool globally on your work machine.

npm install @medusajs/medusa-cli -g
Enter fullscreen mode Exit fullscreen mode

Install Medusa server

Install the Medusa server in your working directory. Name it medusa-store and seed the server with data.

medusa new medusa-store --seed
Enter fullscreen mode Exit fullscreen mode

Configure CORS Settings

In your text editor open up medusa-store/medusa-config.js file and update it to allow the Stackbit client to consume the Medusa Store API.

...
// CORS to avoid issues when consuming Medusa from a client
const STORE_CORS = process.env.STORE_CORS || "http://localhost:8000,http://localhost:3000,http://localhost:8090";
...
Enter fullscreen mode Exit fullscreen mode

Start Medusa server

cd medusa-store
medusa develop
Enter fullscreen mode Exit fullscreen mode

Your Medusa server is now running on port 9000.

Test Medusa server

curl localhost:9000/store/products
Enter fullscreen mode Exit fullscreen mode

***OPTIONAL:
You can also visit localhost:9000/store/products in your browser to test your Medusa server.

The response from the Medusa server should be a large JSON payload. Here are the first few lines of the response:

{
    "products": [
        {
            "id": "prod_01GQTERP2S14W3MJFYAMG7AB7S4",
            "created_at": "2023-02-04T08:35:19.000Z",
            "updated_at": "2023-02-04T08:35:19.000Z",
            "deleted_at": null,
            "title": "Medusa Coffee Mug",
            "subtitle": null,
            "description": "Every programmer's best friend.",
            "handle": "coffee-mug",
                        ...
Enter fullscreen mode Exit fullscreen mode

The Medusa Quickstart guide has more information related to setting up your server.

Next.js Starter Setup

The ecommerce backend is up and running. Next, you will set up the frontend for your ecommerce store. For the frontend, we will use Stackbit’s Next.js Starter and turn it into a storefront.

It is a nice minimal starting point for you to modify into a storefront. It is already configured to work with Stackbit.

Install Starter

In a new working directory separate from medusa-store, create a project for the starter.

npx create-stackbit-app@latest --starter nextjs
Enter fullscreen mode Exit fullscreen mode

This creates a new repo in a my-stackbit-site folder and installs the dependencies for the starter.

Folder Structure

Take a look at the directory structure of the Next.js starter project folder.

cd my-stackbit-site
tree -L 1 -a
Enter fullscreen mode Exit fullscreen mode

Here’s a top level view.

.
├── components
├── content
├── .eslintrc.json
├── .git
├── .gitignore
├── LICENSE
├── netlify.toml
├── .next
├── next.config.js
├── node_modules
├── .nvmrc
├── package.json
├── package-lock.json
├── pages
├── .prettierrc
├── public
├── README.md
├── .stackbit
├── stackbit.config.js
├── styles
└── utils
Enter fullscreen mode Exit fullscreen mode
  • The components folder contains the components that you use to build the pages.
  • The content folder contains the content (markdown files) for the starter.
  • The pages folder has the [[...slug]].js file which is a catch-all page template that maps content to the specific component as defined in the components directory.
  • The .stackbit folder has the models folder which contains models which provide the schema definition for Stackbit to understand your content
  • The stackbit.config.js file enables you to customize the visual editing experience of your site. It is already set with the minimal configuration for the Next.js site.
  • The utils folder has a content.js file which contains a set of utility functions for reading and parsing the content files in the content folder.

Launch the site

Run the Next.js development server in your starter project folder.

npm run dev
Enter fullscreen mode Exit fullscreen mode

This starts up your Next.js development server on port 3000.

Visit localhost:3000 in your browser to see the home page.

Stackbit Next.js Starter Home Page

Stackbit Setup

Stackbit uses a content-driven architecture where content and code are separate from each other. To set up a local development server for Stackbit you need to install the Stackbit CLI. You will then use the stackbit dev command to run the development environment.

The Stackbit development server runs in parallel with the Next.js development server to create the visual editing environment.

Install Stackbit CLI

npm install -g @stackbit/cli
Enter fullscreen mode Exit fullscreen mode

Run Stackbit Dev

Ensure your Next.js development server is still running on port 3000. Navigate to my-stackbit-site and start the Stackbit development server.

cd my-stackbit-site
stackbit dev
Enter fullscreen mode Exit fullscreen mode

Both servers should be running in parallel. The Stackbit server will be running on port 8090 start forwarding requests to http://localhost:3000. Open http://localhost:8090/_stackbit in your web browser and register or sign in to your Stackbit account.

After signing in to your account, you should be met with Stackbit’s visual editor containing your Next.js project.

Stackbit Visual Editor

Editing Content using Stackbit

Add Header

We are going to start editing content using Stackbit by doing a preliminary setup first. You will first add a model for the Header.

In a new terminal session in the my-stackbit-site directory, create a new file named .stackbit/models/HeaderConfig.js

cd my-stackbit-site
touch .stackbit/models/HeaderConfig.js
Enter fullscreen mode Exit fullscreen mode

In your text editor, add the following code to your header model.

module.exports = {
    label: 'Header Config',
    labelField: 'title',
    fields: [
        { type: 'string', name: 'title', label: 'Title', default: 'Your Brand' },
        {
            type: 'list',
            name: 'navLinks',
            label: 'Navigation Links',
            items: {
                type: 'object',
                fields: [
                    { type: 'string', name: 'label', label: 'Label' },
                    { type: 'string', name: 'url', label: 'URL' },
                ],
            },
        },
    ],
  };
Enter fullscreen mode Exit fullscreen mode

This tells Stackbit that the Header component will have two fields: title for the website’s title to be displayed on the Header and navLink for the links to the pages in the website.

Add a component for the Header.

touch components/Header.jsx
Enter fullscreen mode Exit fullscreen mode

In your text editor, add the following code to your Header component.

// components/Header.jsx
export const Header = ({ siteConfig }) => {
    const headerObjectId = siteConfig.__id + ':header';
    return (
        <header className="header outer" data-sb-field-path={headerObjectId}>
            <div className="inner">
                <span className="header-title" data-sb-field-path=".title">{siteConfig.header.title}</span>
                <span className="header-nav" data-sb-field-path=".navLinks">
                    {siteConfig.header.navLinks.map((navLink, idx) => (
                        <a href={`${navLink.url}`} data-sb-field-path={`.${idx}`}>{navLink.label}</a>
                    ))}
                </span>
            </div>
        </header>
    );
};
Enter fullscreen mode Exit fullscreen mode

This will render a header with the site title and navigation links to pages. The data-sb-field-path attribute is used to enable inline editing for the elements in the header.

Next, edit SiteConfig.js in the .stackbit/models directory, to make the Header component accessible to all pages of the website.

// .stackbit/models/SiteConfig.js
module.exports = {
  type: 'data',
  label: 'Site Config',
  singleInstance: true,
  fields: [
    { type: 'string', name: 'title', label: 'Site Title' },
    {
      type: 'model',
      name: 'footer',
      label: 'Footer Config',
      models: ['FooterConfig'],
    },
    {
      type: 'model',
      name: 'header',
      label: 'Header Config',
      models: ['HeaderConfig'],
    },
  ],
};
Enter fullscreen mode Exit fullscreen mode

Import your Header model into the stackbit.config.js file in the root of your my-stackbit-site directory. This is the main configuration file for Stackbit.

// stackbit.config.js
import Button from './.stackbit/models/Button';
import Card from './.stackbit/models/Card';
import Page from './.stackbit/models/Page';
import CardGridSection from './.stackbit/models/CardGridSection';
import FooterConfig from './.stackbit/models/FooterConfig';
import HeroSection from './.stackbit/models/HeroSection';
import SiteConfig from './.stackbit/models/SiteConfig';
import HeaderConfig from './.stackbit/models/HeaderConfig';

const sbConfig = {
  stackbitVersion: '~0.6.0',
  ssgName: 'nextjs',
  cmsName: 'git',
  nodeVersion: '16',
  dataDir: 'content/data',
  pagesDir: 'content/pages',
  pageLayoutKey: 'type',
  assets: {
    referenceType: 'static',
    staticDir: 'public',
    uploadDir: 'images',
    publicPath: '/',
  },
  models: {
    Page,
    Button,
    Card,
    CardGridSection,
    FooterConfig,
    HeroSection,
    SiteConfig,
    HeaderConfig,
  },
};

export default sbConfig;
Enter fullscreen mode Exit fullscreen mode

Add sample data to appear on your Header using the content/data/config.json file. Give your site the title : “Medusa + Stackbit” and add the navLinks for Home and the Store page.

{
    "type": "SiteConfig",
    "title": "Stackbit",
    "footer": {
        "type": "FooterConfig",
        "body": "Made by [Stackbit](https://www.stackbit.com/)\n"
    },
    "header": {
        "type": "HeaderConfig",
        "title": "Medusa + Stackbit",
        "navLinks": [
            {
                "label": "Home",
                "url": "/"
            },
            {
                "label": "Store",
                "url": "/store"
            }
        ]
    }
}
Enter fullscreen mode Exit fullscreen mode

Update the pages/[[...slug]].js file to render the Header when you load your Stackbit site.

// pages/[[...slug]].js
import Head from 'next/head';

import { DynamicComponent } from '../components/DynamicComponent';
import { Footer } from '../components/Footer';
import { Header } from '../components/Header';
import { pagesByType, siteConfig, urlToContent } from '../utils/content';

const FlexiblePage = ({ page, siteConfig }) => {
  return (
    <div className="page">
      <Head>
        <title>{page.title}</title>
      </Head>
      <Header siteConfig={siteConfig} />
      <div data-sb-object-id={page.__id}>
        {page.sections?.length > 0 && (
          <div data-sb-field-path="sections">
            {page.sections.map((section, index) => (
              <DynamicComponent
                key={index}
                {...section}
                data-sb-field-path={`.${index}`}
              />
            ))}
          </div>
        )}
      </div>
      <Footer siteConfig={siteConfig} />
    </div>
  );
};

export default FlexiblePage;

export function getStaticProps({ params }) {
  const url = '/' + (params.slug || []).join('/');
  return { props: { page: urlToContent(url), siteConfig: siteConfig() } };
}

export function getStaticPaths() {
  const pages = pagesByType('Page');
  return {
    paths: Object.keys(pages),
    fallback: false,
  };
}
Enter fullscreen mode Exit fullscreen mode

Add some styling to your header by adding the following lines at the end of styles/styles.css.

/* ---- Header ---- */
.header {
  background: var(--color-blue);
  font-size: 1.5rem;
  font-weight: 800;
  color: var(--color-white);
  padding: 2rem 0rem;
}

.header-nav {
  float: right;
}

.header-nav a {
  padding-right: 1rem;
}

.header-nav a:hover {
  color: var(--color-yellow);
}
Enter fullscreen mode Exit fullscreen mode

Test out your header page. It should be similar to this:

Header for Medusa + Stackbit site

Edit Hero Section

Now, that you have seen how to set up an editable component for your content editor, the next step is to edit the Hero section to see inline editing in action.

Change the message “Welcome to Stackbit” to “Welcome to Medusa + Stackbit!” in the Hero section.

Edit Hero for Medusa + Stackbit Site

This is the ease with which you can visually edit the content on your site without tinkering with any code.

Add Store Page

Next, let’s add a store page to retrieve and display products from our Medusa ecommerce store. Notice that the Store link in the header of your site does not work since we have yet to create a Store page.

Click on the Add New Page(white and blue +) icon at the top of your visual editor. Give your page the title Store and Slug /store and save by clicking + Add. This will create a blank page with the title Store.

Add New Page

If you now click Store on the header it will navigate to the blank page.

Add Model for AllProducts

To add functionality to your Store page, you will need to create a model called AllProducts.js in the .stackbit/models directory. This provides the structure for the content to be displayed on the Store page.

In your terminal create AllProducts.js.

touch .stackbit/models/AllProducts.js
Enter fullscreen mode Exit fullscreen mode

In your text editor, add the following code to AllProducts.js

// stackbit/models/AllProducts.js
module.exports = {
    label: 'All Products',
    groups: ['SectionComponents'],
    fields: [
        { type: 'string', name: 'heading', default: 'All Products' },
        {
            name: 'products',
            type: 'list',
            items: {
                type: 'object',
                fields: [
                    { type: 'string', name: 'name' },
                    { type: 'string', name: 'id' },
                    { type: 'string', name: 'description' },
                    { type: 'string', name: 'price' },
                ],
            },
        },
    ],
}
Enter fullscreen mode Exit fullscreen mode

For simplicity, the AllProducts model has two fields of interest: a heading for the Store page heading and array of products.

Import the AllProducts.js model you just created into your stackbit.config.js file. The updated stackbit.config.js file should be as follows:

// stackbit.config.js
import Button from './.stackbit/models/Button';
import Card from './.stackbit/models/Card';
import Page from './.stackbit/models/Page';
import CardGridSection from './.stackbit/models/CardGridSection';
import FooterConfig from './.stackbit/models/FooterConfig';
import HeroSection from './.stackbit/models/HeroSection';
import SiteConfig from './.stackbit/models/SiteConfig';
import AllProducts from './.stackbit/models/AllProducts';
import HeaderConfig from './.stackbit/models/HeaderConfig';

const sbConfig = {
  stackbitVersion: '~0.6.0',
  ssgName: 'nextjs',
  cmsName: 'git',
  nodeVersion: '16',
  dataDir: 'content/data',
  pagesDir: 'content/pages',
  pageLayoutKey: 'type',
  assets: {
    referenceType: 'static',
    staticDir: 'public',
    uploadDir: 'images',
    publicPath: '/',
  },
  models: {
    Page,
    Button,
    Card,
    CardGridSection,
    FooterConfig,
    HeroSection,
    SiteConfig,
    AllProducts,
    HeaderConfig,
  },
};

export default sbConfig;
Enter fullscreen mode Exit fullscreen mode

Next, create an AllProducts component that uses the AllProducts model in the components folder. This component will fetch all the products from the Medusa Server and display them on the Store page.

touch components/AllProducts.jsx
Enter fullscreen mode Exit fullscreen mode

Add the following code to AllProducts.jsx

// components/AllProducts.jsx
import { useState, useEffect } from "react";
import Link from 'next/link';

export const AllProducts = (props) => {
  const [products, setProducts] = useState([]);

  useEffect(() => {
    const fetchProducts = async () => {
      const response = await fetch('http://localhost:9000/store/products');
      const data = await response.json();
      setProducts(data.products);
    };
    fetchProducts();
  }, []);

  return (
    <div data-sb-field-path={props['data-sb-field-path']}>
      <div className="outer heading-box">
        <div className="inner heading-box">
          <h1 className="store-heading" data-sb-field-path=".heading">{props.heading}</h1>
        </div>
      </div>
      <div className="outer store-box">
        <div className="inner store-box">
          <ul className="store-list">
            {products.map((product) => (
              <li key={product.id}>
                <Link href={`/store/${product.id}`}>
                  <h2>{product.title}</h2>
                  <img src={`${product.thumbnail}`} width="250"></img>
                  <p>${product.variants[0].prices[0].amount}</p>
                </Link>
              </li>
            ))}
          </ul>
        </div>
      </div>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

This component retrieves the products from the Medusa Server at localhost:9000/store/products and updates the products state variable with the fetched products. The products are then rendered with a link to each individual product’s page which we will need to create.

To make the AllProducts component function, you must import it into the DynamicComponent component. Update DynamicComponent as follows:

// components/DynamicComponent.jsx
import { HeroSection } from './HeroSection';
import { CardGridSection } from './CardGridSection';
import { AllProducts } from './AllProducts';

const componentsMap = {
  HeroSection: HeroSection,
  CardGridSection: CardGridSection,
  AllProducts: AllProducts,
};

export const DynamicComponent = (props) => {
  if (!props.type) {
    const propsOutput = JSON.stringify(props, null, 2);
    throw new Error(`Object does not have a 'type' property: ${propsOutput}`);
  }

  const Component = componentsMap[props.type];
  if (!Component) {
    throw new Error(`No component is registered for type:'${props.type}`);
  }
  return <Component {...props} />;
};
Enter fullscreen mode Exit fullscreen mode

Add the styling for the Store page. Add the following lines at the end of styles/style.css.

/* ---- Store ---- */
.outer.heading-box {
  background-image: url("/bg.svg");
  background-repeat: repeat;
  background-color: rgb(193, 189, 189);
  padding: 5rem 0rem;
}

.inner.heading-box {
  text-align: center;
  font-size: 2.4rem;
  font-weight: 800;
  color: rgb(4, 4, 83);
}

.inner.store-box {
  text-align: center;
  padding: 1rem 0rem;
}

ul.store-list {
  list-style: none;
}

ul.store-list p {
  font-size: 1.5rem;
  color: darkblue;
}
Enter fullscreen mode Exit fullscreen mode

Now click on the Store page link on the site header. You should see a list of all products from the Medusa Server.

Store Page

The Store page loads well with a list of all products. However, clicking on each product gives a 404 Error.

Add Single Product Page

To create individual product pages for each product, we will use Next.js’s dynamic routing feature.

Create a new directory named store in the pages directory of your my-stackbit-site folder. In that directory create a new page file called [productId].jsx. This will be a template for the individual product pages.

mkdir pages/store && touch pages/store/[productId].jsx
Enter fullscreen mode Exit fullscreen mode

Add the following code to productId.jsx :

// pages/store/[productId].jsx
import { useRouter } from 'next/router';
import { useState, useEffect } from 'react';

const ProductPage = () => {
  const router = useRouter();
  const { productId } = router.query;
  const [product, setProduct] = useState(null);

  useEffect(() => {
    const fetchProduct = async () => {
      const response = await fetch(`http://localhost:9000/store/products/${productId}`);
      const data = await response.json();
      setProduct(data.product);
    };
    fetchProduct();
  }, [productId]);

  if (!product) {
    return <div>Loading...</div>;
  }

  return (
    <div className="outer product-box">
      <div className="inner product-box">
        <h1>{product.title}</h1>
        <img src={`${product.thumbnail}`} width="320"></img>
        <p className="description">{product.description}</p>
        <p className="price">${product.variants[0].prices[0].amount}</p>
      </div>
    </div>
  );
};

export default ProductPage;
Enter fullscreen mode Exit fullscreen mode

The useRouter hook is used to get the id of the product from the URL, then uses that id to fetch data for the individual product. When a user clicks on a product link on the Store page, they will be taken to an individual product page with the corresponding data fetched from the Medusa server.

Next, add some styling to your ProductPage by adding the following CSS code at the end of the styles/style.css file.

/*---- Individual Product ----*/
.inner.product-box {
  text-align: center;
  padding: 3rem 1rem;
}

.price {
  color: darkblue;
  font-size: 1.5rem;
}
Enter fullscreen mode Exit fullscreen mode

Preview the product page by clicking on one of the products in the Store page.

Product Page

Add Cart

The Medusa backend can handle the creation and storage of the cart, the retrieval of the cart, adding, updating, and deleting line items from the cart. This frees frontend developers from having to write lots of logic to manage the state of your cart. Check out How to Add Cart functionality | Medusa to learn more about carts in Medusa.

For this tutorial, we will just be implementing the creation of the cart and adding line items to the cart.

First, add a link to the Cart in the header by updating the content/data/config.json file.

{
    "type": "SiteConfig",
    "title": "Stackbit",
    "footer": {
        "type": "FooterConfig",
        "body": "Made by [Stackbit](https://www.stackbit.com/)\n"
    },
    "header": {
        "type": "HeaderConfig",
        "title": "Medusa + Stackbit",
        "navLinks": [
            {
                "label": "Home",
                "url": "/"
            },
            {
                "label": "Store",
                "url": "/store"
            },
            {
                "label": "Cart",
                "url": "/cart"
            }
        ]
    }
}
Enter fullscreen mode Exit fullscreen mode

If you refresh your site, you should see a link to the Cart in the page header.

Link to Cart

Next, create the Cart page which will create a new cart or retrieve a pre-existing cart from localStorage, display the list of products in the cart, and has an addProductToCart function for adding new line items to the cart.

Create a new file named cart.js in the pages folder.

touch pages/cart.js
Enter fullscreen mode Exit fullscreen mode

Add the following code to pages/cart.js :

// pages/cart.js
import React, { useState, useEffect } from "react";

export default function Cart() {
  const [cart, setCart] = useState(null);

  useEffect(() => {
    const id = localStorage.getItem("cart_id");
    if (id) {
      fetch(`http://localhost:9000/store/carts/${id}`, {
        credentials: "include",
      })
        .then((response) => response.json())
        .then(({ cart }) => setCart(cart))
    } else {
      fetch(`http://localhost:9000/store/carts`, {
        method: "POST",
        credentials: "include",
      })
        .then((response) => response.json())
        .then(({ cart }) => {
          setCart(cart);
          localStorage.setItem("cart_id", cart.id);
        });
    }
  }, []);

  if (!cart) {
    return <div>Loading Cart...</div>;
  }

  return (
    <>
     <div className="outer cart-box">
          <div className="inner cart-box">
              <h2>Your Cart</h2>
              <table>
                  <thead>
                      <tr>
                          <th>Item</th>
                          <th>Unit Price</th>
                          <th>Quantity</th>
                      </tr>
                  </thead>
                  <tbody>
                      {cart.items.map((item) => (
                          <tr key={item.id}>
                              <td>{item.title}</td>
                              <td>${item.unit_price}</td>
                              <td>{item.quantity}</td>
                          </tr>
                      ))}
                  </tbody>
              </table>
          </div>
      </div>
    </>
  );
};

export const addProductToCart = (variant_id) => {
  const id = localStorage.getItem("cart_id");
  if (id) {
    fetch(`http://localhost:9000/store/carts/${id}/line-items`, {
      method: "POST",
      credentials: "include",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        variant_id,
        quantity: 1,
      }),
    })
      .then((response) => response.json())
      .then(({ cart }) => console.log(cart));
  }
};
Enter fullscreen mode Exit fullscreen mode

This is a simple cart to illustrate the power of Medusa’s ecommerce backend. The useEffect is used to fetch a cart using the id of the cart, if it doesn’t exist it will create one and parse it to cart array. The cart is then rendered with the list of products.

Next, add some styling to the cart page by adding the following code at the end of styles/style.css

/*---- Cart ----*/
.outer.cart-box {
  padding: 2rem 1rem;
}

.cart-box li {
  list-style: none;
  padding-bottom: 1rem;
}

/*---- Table ----*/
table {
  border-collapse: collapse;
  border: 3px solid rgb(200,200,200);
  letter-spacing: 1.5px;
  font-size: 1rem;
}

td, th {
  border: 1px solid rgb(190,190,190);
  padding: 10px 20px;
}

th {
  background-color: rgb(235,235,235);
}

td {
  text-align: center;
}

tr:nth-child(even) td {
  background-color: rgb(250,250,250);
}

tr:nth-child(odd) td {
  background-color: rgb(245,245,245);
}

caption {
  padding: 10px;
}
Enter fullscreen mode Exit fullscreen mode

To test out the cart, you will need to import the addProductToCart function into the AllProducts component.

Update the AllProducts component with the following code:

// components/AllProducts.jsx
import { useState, useEffect } from "react";
import Link from 'next/link';
import { addProductToCart } from '../pages/cart';

export const AllProducts = (props) => {
  const [products, setProducts] = useState([]);

  useEffect(() => {
    const fetchProducts = async () => {
      const response = await fetch('http://localhost:9000/store/products');
      const data = await response.json();
      setProducts(data.products);
    };
    fetchProducts();
  }, []);

  return (
    <div data-sb-field-path={props['data-sb-field-path']}>
      <div className="outer heading-box">
        <div className="inner heading-box">
          <h1 className="store-heading" data-sb-field-path=".heading">{props.heading}</h1>
        </div>
      </div>
      <div className="outer store-box">
        <div className="inner store-box">
          <ul className="store-list">
            {products.map((product) => (
              <li key={product.id}>
                <Link href={`/store/${product.id}`}>
                  <h2>{product.title}</h2>
                  <img src={`${product.thumbnail}`} width="250"></img>
                  <p>${product.variants[0].prices[0].amount}</p>
                </Link>
                <button className="button" onClick={() => { 
                  addProductToCart(product.variants[0].id); 
                  alert('Added to Cart');
                  }}>Add to Cart</button>
              </li>
            ))}
          </ul>
        </div>
      </div>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

We have added an Add to Cart button which implements the addProductToCart function for each product. Reload your Store page, to see the changes take effect.

Add to Cart Button

Click on the Add to Cart button for any product, then navigate to the Cart page and you will find the items you just added to your cart list. Here’s an example:

Cart page with added products

Preview Site

After adding all these features, the next step is to preview your site. Stackbit provides a Preview option in the Visual Content Editor for you to test out the site.

Stackbit Preview feature

Feel free to toggle between the Preview button and the Edit button and tinker with your site.

Deployment

The next step is the process of deployment. For in-depth guides on deploying the Medusa server use Medusa Server Deployment Guides.

This tutorial did not cover creating a Stackbit cloud project. This is useful when you want to introduce content editors to your site. Check out Create Stackbit from GitHub for more information.

As for deploying your storefront, you can use Vercel or Netlify. Check out these articles:

Conclusion

In this tutorial, we learned how to create an e-commerce storefront using Medusa and Stackbit.

We explored the visual editing experience that Stackbit provides as well as the feature-rich Medusa ecommerce backend.

We added a Store page that displays all products from the Medusa server and added individual product pages for each product using Next.js dynamic routing. We also added a simple cart that displays the list of products. Finally, we previewed the site and discussed deployment options.

As you continue to work with Stackbit and Medusa, there are many additional features and customizations that you can explore. For example, you can add more complex cart and checkout functionality, integrate payment providers and create a custom design for your storefront.

It's important to remember that this tutorial only scratched the surface of what is possible with these powerful tools. However, it should provide you with a solid foundation to build upon and experiment with.

If you run into any issues or have questions, feel free to reference the extensive documentation available for both Stackbit and Medusa or to seek help from the vibrant communities surrounding these tools.

Happy building!

Top comments (1)

Collapse
 
shahednasser profile image
Shahed Nasser

Nice tutorial thank you for writing it!