How to Build an Electronic Commerce Store with Medusa and Nextjs

November 1, 2023, . open book19 minutes read






Introduction

In this tutorial, you will learn how to build an electronic headless commerce store with Medusa and Nextjs as the storefront.

The demand and supply of goods and services keep evolving daily. Businesses need to keep an eye on these evolutions to adopt the right ecommerce platform to distribute their goods and services to various end users.

Headless commerce is becoming increasingly popular because of its adaptability and scalability. A great option for constructing a headless commerce store is Medusa, an open-source and scalable ecommerce engine. Next.js offers a robust framework for building storefronts.

With the help of Medusa and Next.js, you can develop a powerful and scalable online store that is tailored to the particular requirements of your company or business. Regardless of your experience as a developer or online retailer, this tutorial will guide you on building and customizing an electronic store.

Here’s a demo of the application with Medusa and Next.js by following this tutorial:

Basically, you will learn how to:

  • Set up a Storefront with NextJs-default starter template
  • Add product using the admin dashboard
  • Display products in your storefront
  • Set up Tailwind CSS
  • Customize your storefront
  • Add a Search option to your store
  • Add Payment option to your store

You can find the code in this GitHub Repository

Why Nextjs

Next.js is an open-source React framework for building user-friendly web applications. React is a JavaScript library for building interactive user interfaces. Nextjs enables server-side rendering and requires minimal or no configuration.

Medusa offers two storefronts the Nextjs and Gatsby storefront starter. It’s easy to install them with the CLI command. Medusa Nextjs storefront comes with many ecommerce store’s pre-build features like a shopping cart, product display, payment processing, and more.

Why Medusa?

Medusa is an open-source composable commerce platform built with Node.js and provides you with many ecommerce features such as RMA Flows, product and collection management, order management, customization, and more. These are some features that set Medusa apart from other ecommerce platforms. Medusa also offers a great developer experience which allows developers to create amazing and scalable stores with minimal effort.

Medusa’s headless architecture enables you to build your store with the framework or language of your choice. There are no constraints as you can use Medusa’s API to connect the Medusa admin to the storefront to perform actions like the product, customer, order, payment management, and many more.

Creating your Electronic Commerce Platform

This section highlights the various steps you will use to build your electronic store with Nextjs and Medusa.

Prerequisites

Before you begin, make sure you have the following:

You will find all the resources used in this tutorial such as icons and images in the git repository

How to Set Up the Medusa Server for the Electronic Commerce Store

If you have everything installed, then follow these steps to set up your Medusa project

1. Install the Medusa CLI tool with the following command:

yarn global add @medusajs/medusa-cli

2. Create your Medusa application with this command:

yanr create-medusa-app

Upon executing, this command will ask you where you which to install your project, enter a name here it’s ElectronicStore and press enter. Then choose Medusa starter. This tutorial uses the default Medusa starter as mentioned earlier.

Normally you should have the same information as the screenshot below.

create medusa-app

Upon the execution of this command, Medusa will create 3 folders in the ElectronicStore; storefront, admin and backend.

To start each part of the application, open the three command line/terminal tabs and run the following command with respect to each folder:

# Medusa server
cd ElectronStore/backend
yarn start

# Admin
cd ElectronStore/admin
yarn start
  
# Storefront
cd ElectronStore/storefront
yarn dev  

After executing those commands, visit localhost:8000 to view your storefront

how to build an electronic headless commerce store with Medusa and Nextjs - medusa storefront

Similarly, visit your admin dashboard by opening your browser on localhost:7000. 7000 is the default admin port.

how to build an electronic headless commerce store with Medusa and Nextjs - medusa admin

The next step consists of styling your storefront. This tutorial uses Tailwind CSS very useful for Rapidly building modern websites

Note that you can equally set up your Medusa admin, Medusa storefront and Medusa Backend separately.

Styling the Electronic Commerce Storefront

In this section, you will learn how to style your storefront using Tailwind CSS. To do so you need to install and configure Tailwind CSS as follows

Add Tailwind CSS

Since it’s a Nextjs application, you will use the official NextJS Integration guide. Add Tailwind to your NextJs store with the following commands

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Next, add the code below to your tailwind.config.js:

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./app/**/*.{js,ts,jsx,tsx}",
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
 
    // Or if using `src` directory:
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Now that you have successfully added Tailwind to your project, you need to customize your storefront.

Customizing the Storefront

Start by changing the style of the header storefront/src/module/layout/templates/nav/index.tsx with the code below:

const Nav = () => {
  const { pathname } = useRouter()
  const [isHome, setIsHome] = useState(false)
  useEffect(() => {
    pathname === "/" ? setIsHome(true) : setIsHome(false)
  }, [pathname])

  const { toggle } = useMobileMenu()

  return (
    <div
      className={clsx("sticky top-0 inset-x-0 z-50 group", {
        "!fixed": isHome,
      })}
    >
      <header className="relative h-16 px-8 mx-auto transition-colors bg-white border-b border-transparent duration-200 group-hover:bg-white group-hover:border-gray-200 border-b-[#f1f1f1]">
        <nav
          className={clsx(
            "text-gray-900 flex items-center justify-between w-full h-full text-small-regular transition-colors duration-200"
          )}
        >
          <div className="flex-1 basis-0 h-full flex items-center">
            <div className="block small:hidden">
              <Hamburger setOpen={toggle} />
            </div>
            <div className="hidden small:block h-full">
              <DropdownMenu />
            </div>
          </div>

          <div className="flex items-center h-full">
            <Link href="/">
              <a className="text-xl-semi uppercase">
                <img src="/logo.png" alt="" width={50} />
              </a>
            </Link>
          </div>

          <div className="flex items-center gap-x-6 h-full flex-1 basis-0 justify-end">
            <div className="hidden small:flex items-center gap-x-6 h-full">
              {process.env.FEATURE_SEARCH_ENABLED && <DesktopSearchModal />}
              <Link href="/account">
                <a>Account</a>
              </Link>
            </div>
            <CartDropdown />
          </div>
        </nav>
        <MobileMenu />
      </header>
    </div>
  )
}

export default Nav

This code above changes the navbar by adding a border and logo.

To change the banner image, locate your storefront/src/modules/home/components/hero/index.tsx which is the hero section and replace it with the code below:

const Hero = () => {
  return (
    <div className="h-[90vh] w-full relative">
      <div className="text-white absolute inset-0 z-10 flex flex-col justify-center items-center text-center small:text-left small:justify-end small:items-start small:p-32">
        <h1 className="text-2xl-semi mb-4 drop-shadow-md shadow-black">
          Hi 👋 welcome to my electronic shop
        </h1>
        <p className="text-base-regular max-w-[32rem] mb-6 drop-shadow-md shadow-black">
          The best electronic devices to help you in your daily tasks both in
          the professional and personal life.
        </p>
        <UnderlineLink href="/store">Explore products</UnderlineLink>
      </div>
      <div className="bg-white h-[90vh] w-full">
        The best electronic devices to help you in your daily tasks both
        in the professional and personal life.
      </div>
      <Image
        src="/hero2.jpg"
        layout="fill"
        loading="eager"
        priority={true}
        quality={90}
        objectFit="cover"
        alt="computer"
        className="absolute inset-0"
        draggable="false"
      />
      <div className="bg-black h-[90vh] w-full absolute top-0 opacity-50"></div>
    </div>
  )
}

export default Hero

This code block adds a banner image and some text to the hero section

Next, slightly change the way you display feature products in the home section. storefront/src/module/home/components/featured-products/index.tsx:

const FeaturedProducts = () => {
  const { data } = useFeaturedProductsQuery()

  return (
    <div className="py-12">
      <div className="content-container py-12">
        <div className="flex flex-col items-center text-center mb-16">
          <span className="text-base-regular text-gray-600 mb-6">
            Latest products
          </span>
          <p className="text-2xl-regular text-gray-900 max-w-lg mb-4">
            Our newest electronic gadgets to make your life smooth.
          </p>
          <UnderlineLink href="/store">Explore products</UnderlineLink>
        </div>
        <ul className="grid grid-cols-2 small:grid-cols-4 gap-x-4 gap-y-8">
          {data
            ? data.map((product) => (
                <li key={product.id}>
                  <ProductPreview {...product} />
                </li>
              ))
            : Array.from(Array(4).keys()).map((i) => (
                <li key={i}>
                  <SkeletonProductPreview />
                </li>
              ))}
        </ul>
      </div>
    </div>
  )
}

export default FeaturedProducts

The code above changes the text of feature products of your store.

Change the section just before the footer storefront/src/modules/layout/components/footer-cta/index.tsx by adding the following code:

const FooterCTA = () => {
  return (
    <div className="bg-gray-100 w-full">
      <div className="content-container flex flex-col-reverse gap-y-8 small:flex-row small:items-center justify-between py-16 relative">
        <div>
          <h3 className="text-2xl-semi">Check out the lastest gadgets</h3>
          <div className="mt-6">
            <UnderlineLink href="/store">Explore products</UnderlineLink>
          </div>
        </div>

        <div className="relative w-full aspect-square small:w-[35%] small:aspect-[28/36]">
          <Image
            src="/hero3.jpg"
            alt=""
            layout="fill"
            objectFit="cover"
            className="absolute inset-0"
          />
        </div>
      </div>
    </div>
  )
}

export default FooterCTA

Here you are changing the default text as well as the default image

Now move to the footer file storefront/src/modules/layout/components/footer-nav/index.tsx and replace it with the following code:

const FooterNav = () => {
  const { collections } = useCollections()

  return (
    <div className="content-container flex flex-col gap-y-8 pt-16 pb-8">
      <div className="flex flex-col gap-y-6 xsmall:flex-row items-start justify-between">
        <div>
          <Link href="/">
            <a className="text-xl-semi uppercase">
              <img src="/logo.png" alt="" width={50} />
            </a>
          </Link>
          <p className="py-5 text-sm text-[#999]">
            Get the best electronics from the best place
          </p>
          <div className="flex flex-col-reverse gap-y-0 justify-center xsmall:items-center xsmall:flex-row xsmall:justify-between">
            <span className="text-xsmall-regular text-gray-500">
              © Copyright 2023 ES store
            </span>
            <div className="min-w-[316px] flex xsmall:justify-end">
              <CountrySelect />
            </div>
          </div>
        </div>
        <div className="text-small-regular grid grid-cols-3 gap-x-16">
          <div className="flex flex-col gap-y-2">
            <span className="text-base-semi">Collections</span>
            <ul
              className={clsx("grid grid-cols-1 gap-y-2", {
                "grid-cols-2": (collections?.length || 0) > 4,
              })}
            >
              {collections?.map((c) => (
                <li key={c.id}>
                  <Link href={`/collections/${c.id}`}>
                    <a>{c.title}</a>
                  </Link>
                </li>
              ))}
            </ul>
          </div>
          <div className="flex flex-col gap-y-2">
            <span className="text-base-semi">Home Utencils</span>
            <ul className="grid grid-cols-1 gap-y-2">
              <li>
                <a href="https://utencils" target="_blank" rel="noreferrer">
                  Water Dispensor
                </a>
              </li>
              <li>
                <a href="https://mat" target="_blank" rel="noreferrer">
                  Cooking mat
                </a>
              </li>
              <li>
                <a href="https://opener" target="_blank" rel="noreferrer">
                  Drink Opener
                </a>
              </li>
            </ul>
          </div>
          <div className="flex flex-col gap-y-2">
            <span className="text-base-semi">Device</span>
            <ul className="grid grid-cols-1 gap-y-2">
              <li>
                <a href="https://computer" target="_blank" rel="noreferrer">
                  Computer
                </a>
              </li>
              <li>
                <a href="https://phone" target="_blank" rel="noreferrer">
                  Phones
                </a>
              </li>
              <li>
                <a href="https://watch" target="_blank" rel="noreferrer">
                  Watch
                </a>
              </li>
            </ul>
          </div>
        </div>
      </div>
    </div>
  )
}

export default FooterNav

This code snippet above adds new links and a logo in the footer.

In the last part of the footer change the background colour storefront/src/modules/layout/components/medusa-cta/index.tsx

const MedusaCTA = () => {
  return (
    <div className="py-4 flex justify-center items-center w-full bg-[#333]">
      <div className="content-container flex justify-center flex-1">
        <a href="https://www.medusajs.com" target="_blank" rel="noreferrer">
          <PoweredBy />
        </a>
      </div>
    </div>
  )
}

Head to storefront/src/pages/index.tsx to modify the meta information of the home page:

const Home: NextPageWithLayout = () => {
  return (
    <>
      <Head
        title="Home"
        description="Get the best electroics for your home and office from the best stores online with secure payment methods."
      />
      <Hero />
      <FeaturedProducts />
    </>
  )
}

By the end, you should have a store similar to the screenshot below

how to build an electronic headless commerce store with Medusa and Nextjs - electronic storefront

Adding Products in the Electronic Commerce Store

By default, Medusa comes with some products on the storefront. So you need to add your own products. Medusa commonly uses a File service plugin to add products to the admin dashboard.

Installing and integrating a File Service Plugin (S3)

Simple Storage Service(S3) is a powerful open-source object storage suite available on any public and private cloud. Medusa uses file service plugins to host files such as images.

This tutorial with use AWS S3 but you can use other file services like Space and Minio. You need to have an AWS account and then create a bucket if you don’t have any. Equally, you need to create an access and secret key that you will use in the next section to build your store

When you are done with that, install the S3 plugin in the backend with the command below:

yarn add medusa-file-s3

Then open your backend/.env file and add the following:

S3_URL=<YOUR_BUCKET_URL>
S3_BUCKET=<YOUR_BUCKET_NAME>
S3_REGION=<YOUR_BUCKET_REGION>
S3_ACCESS_KEY_ID=<YOUR_ACCESS_KEY_ID>
S3_SECRET_ACCESS_KEY=<YOUR_SECRET_ACCESS_KEY>
  • <YOUR_BUCKET_URL> corresponds to the URL of your bucket it’s generally in the form: `http://<YOUR_BUCKET_NAME>.s3-website-<YOUR_BUCKET_REGION>.amazonaws.com`
  • <YOUR_BUCKET_NAME> in the name of your S3 bucket
  • <YOUR_ACCESS_KEY_ID your access key was created earlier
  • <YOUR_SECRET_ACCESS_KEY> is your secret key created earlier

To complete the S3 integration, add the following configuration to the array of plugins in the backend/medusa-cOnceonfig.js file:

const plugins = [
  // ...
  {
     resolve: `medusa-file-s3`,
    options: {
      s3_url: process.env.S3_URL,
      bucket: process.env.S3_BUCKET,
      region: process.env.S3_REGION,
      access_key_id: process.env.S3_ACCESS_KEY_ID,
      secret_access_key: process.env.S3_SECRET_ACCESS_KEY,
    },
  },
]

Lastly, edit storefront/next.config.js file like this:

images: {
    domains: ["<YOUR_BUCKET_NAME>.s3.amazonaws.com"],
  },

Add the domain from where you are uploading your images. <YOUR_BUCKET_NAME> corresponds to the name of your bucket.

Once you have finished setting up S3 you can now start adding electronic products to your store

Add products with Medusa Admin

To add products to your store, head to the admin dashboard(localhost:7000). The default credential to log in is admin@medusa-test.com and supersecret. The Medusa admin dashboard connects Medusa Server to help you manage your store.

Once you log in, you should have a similar interface as below, you can manage your products, orders, customer settings and more

how to build an electronic headless commerce store with Medusa and Nextjs

Head to the product page and delete all the default products by clicking on the three dots at the extreme end and selecting delete.

After removing all the products, click on add new product button to add some electronic products to the ecommerce store.

how to build an electronic headless commerce store with Medusa and Nextjs

If you go to localhost:8000/store you will see your newly added products.

how to build an electronic headless commerce store with Medusa and Nextjs - products in electronic store

Integration

Here you will be integrating two main services; Search and Payment.

Algolia Integration

Algolia is an open-source UI search JavaScript library. It allows you to add advanced search functionalities to your store. Adding this search functionality to your store will greatly improve the user experience and optimize internal search in the electronic commerce store.

To add Algolia to your application you need to possess an Algolia account. Upon creating your account, follow this documentation to create an Algolia application and then, get your Algolia Application ID, Admin API Key, and Search-Only API Key.

Next, install the Algolia plugin in your store with the following command:

yarn add medusa-plugin-algolia

Then add this environment variable to your Medusa backend:

ALGOLIA_APP_ID=<YOUR_APP_ID>
ALGOLIA_ADMIN_API_KEY=<YOUR_ADMIN_API_KEY>

<YOUR_APP_ID> and <YOUR_ADMIN_API_KEY> correspond to the Application ID and Admin API Key mentioned earlier. You can find them on the API Keys page under your Algolia account.

Add the following item to the plugins array in medusa-config.js:

const plugins = [
  // ...
  {
    resolve: `medusa-plugin-algolia`,
    options: {
      application_id: process.env.ALGOLIA_APP_ID,
      admin_api_key: process.env.ALGOLIA_ADMIN_API_KEY,
      settings: {
        products: {
          searchableAttributes: ["title", "description"],
          attributesToRetrieve: [
            "id",
            "title",
            "description",
            "handle",
            "thumbnail",
            "variants",
            "variant_sku",
            "options",
            "collection_title",
            "collection_handle",
            "images",
          ],
        },
      },
    },
  },
]

searchableAttributes are attributes in a product that is searchable while attributesToRetrieve are attributes to retrieve for each product result.

Now let’s add the search UI to the storefront so that users can effectively perform product research. Fortunately, NextJs storefront Algolia integration is available out-of-the-box. To get everything ready, make sure the search feature is set to true in the store.config.json.:

 {
  "features": {
    "search": true
  }
}

Then add the environmental variables:

NEXT_PUBLIC_SEARCH_APP_ID=<YOUR_APP_ID>
NEXT_PUBLIC_SEARCH_API_KEY=<YOUR_SEARCH_API_KEY>
NEXT_PUBLIC_SEARCH_INDEX_NAME=products
  • <YOUR_APP_ID> corresponds to the Application ID
  • <YOUR_SEARCH_API_KEY> corresponds to the Search-Only API Key. You can find this information in your Algolia API Keys section

To complete this integration, change the code in src/lib/search-client.ts to the following:

import algoliasearch from "algoliasearch/lite"

const appId = process.env.NEXT_PUBLIC_SEARCH_APP_ID || ""

const apiKey = 
  process.env.NEXT_PUBLIC_SEARCH_API_KEY || "test_key"

export const searchClient = algoliasearch(appId, apiKey)

export const SEARCH_INDEX_NAME =
  process.env.NEXT_PUBLIC_INDEX_NAME || "products"

If you run your storefront and backend, you will notice that the search feature is available.

how to build an electronic headless commerce store with Medusa and Nextjs - algolia search

Stripe Integration

This section will guide you through how to set up Stripe payment methods in your Medusa backend, admin, and storefront. Stripe is one of the simplest ways to accept online payments. It gives you the technical components needed to manage transactions safely. Other payment methods you can is with Medua are Paystack, Klarna and Paypal

To add Stripe to your store you need to possess a Stripe account. Same as Algolia, you need to retrieve the API Keys and secrets from your account to connect Medusa to your Stripe account.

In the root directory of the Medusa backend, use the following command to install the Stripe plugin:

yarn add medusa-payment-stripe

Next, you need to add configurations to your stripe plugin. In medusa-config.js, add the following at the end of the plugins array:

const plugins = [
  // ...
  {
    resolve: `medusa-payment-stripe`,
    options: {
      api_key: process.env.STRIPE_API_KEY,
      webhook_secret: process.env.STRIPE_WEBHOOK_SECRET,
    },
  },
]

Head to the dashboard of your Stripe account to retrieve the API Key and secret key.

Then, in your Medusa backend, create .env if it does not exist and add the Stripe key:

STRIPE_API_KEY=sk_...

Next, add Stripe’s webhook in the .env file. You can get it by heading to the Stripe dashboard and clicking on Add an Endpoint. Medusa backend’s endpoint Stripe webhook is {BACKEND_URL}/stripe/hooks.

Note: Add this endpoint to its field if you are in production and make sure to replace {BACKEND_URL} with the URL of your backend. Add a description and select at least one action then click on Add endpoint. Once the Webhook is created, you’ll see “Signing secret” in the Webhook details. Reveals the secret key by clicking on reveal it and copying it into your Medusa backend.

STRIPE_WEBHOOK_SECRET=whsec_...

The last part of Stripe integration consists of setting it up in the Medusa admin by adding a payment provider.

  1. Go to Settings → Regions.
  2. Select a region to edit.
  3. Click on the horizontal icon at the top right of the first section on the right.
  4. Click on Edit Region Details from the dropdown.
  5. Under the provider’s section, select the payment providers you want to add to the region. Here it’s Stripe.
  6. Unselect the payment providers you want to remove from the region
  7. .Click Save.

Finally, in your storefront, add the following environment variable

NEXT_PUBLIC_STRIPE_KEY=<YOUR_PUBLISHABLE_KEY>

Where <YOUR_PUBLISHABLE_KEY> corresponds to your Stripe Publishable Key.

If you run your Medusa storefront and backend, add products to your cart and then proceed to checkout you will now be able to use Stripe as a payment method

Result:

how to build an electronic headless commerce store with Medusa and Nextjs - stripe payment

You could also check out this stripe guide for more information regarding its integration

Testing the Store

The final step of this tutorial consists of testing the whole store. While the Medusa server is still running, restart your storefront.

Navigate to localhost:8000 to see the final electronic commerce store

how to build an electronic headless commerce store with Medusa and Nextjs - storefront

Click on the Store item in the navigation bar to open the store:

store products

Add some products to your cart:

how to build an electronic headless commerce store with Medusa and Nextjs - shopping back

Checkout:

payment with stripe

View your order:

how to build an electronic headless commerce store with Medusa and Nextjs order

Final Thoughts

In this tutorial, you learned how to build a headless electronic commerce platform with the Medusa engine, using the Nextjs Storefront and integrating services like search and payment using Algolia and Stripe respectively.

However, there are many other functionalities you can add to this electronic commerce store such as:

Get straight to the Medusa documentation for more details regarding these functionalities and services.

Should you have any issues or questions related to Medusa, then feel free to reach out to the Medusa team via Discord.


Share on



Author: Learndevtools

Like the article? Please share or subscribe to get more updates from Learndevtools