Remix & Shopify: Circumvent Shopify’s APIs and go open source
Tags: opensource, javascript, webdev, programming
Published: 2022-11-01 12:06:53

Shopify seems to acknowledge that open source is what developers want. First, they gave us Hydrogen and now they went even further by [joining forces with Remix](https://remix.run/blog/remixing-shopify). While good news, some might still want to go all-in on open source and customizability by building with a setup that is fully open source across the entire stack. 

In this article, you’ll learn how you can circumvent Shopify to get a fully functional ecommerce setup with Remix using only open-source tools. Win-win-win.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nuktc8ci14nh7hpnfp9r.gif)

### How it’ll work

First, we will replace Shopify’s functionality by combining the power of two of the most popular open source projects:

- [Medusa](https://medusajs.com/), the leading open-source ecommerce platform for developers allowing for great customization and is built with Javascript
- [Strapi](https://strapi.io/), the leading open source CMS built for developers likewise highly customizable and also built in Javascript

In this tutorial, you'll learn how to build an ecommerce storefront using Remix and powered by Medusa & Strapi to avoid a full Shopify backend. 

You can find the source code for this article [in this repository](https://github.com/AviAvinav/medusa-remix-ecommerce). Below a sneak peak to the final result:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/npfxr8b58x8alx5f0gkf.gif)

## Prerequisites

- [Node v14 or above](https://docs.medusajs.com/tutorial/set-up-your-development-environment/#nodejs)
- [Yarn](https://classic.yarnpkg.com/lang/en/docs/install/) is recommended, but you can also follow along with npm.
- [Redis](https://docs.medusajs.com/tutorial/set-up-your-development-environment/#redis)
- Medusa CLI: To install the CLI, run `yarn global add @medusajs/medusa-cli`.

## Set Up Strapi

### Install the Template

```bash
npx create-strapi-app strapi-medusa --template shahednasser/strapi-medusa-template
```

This creates a folder named `strapi-medusa` in your project. Once the installation is complete, the Strapi development server will start on port `localhost:1337`. A new page will also open in your default browser to create a new admin user and log in. After you have logged in, you can access your Strapi Dashboard.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f2dz8psjhbqdpes0apr4.png)

### Change Authorization Settings for the User

Your Medusa sever will require the credentials of a Strapi User in order to seed Strapi with mock data. To create a new user, go to Content Manager, then choose User under Collection Types.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cmrh017xr69ykd9jkl20.png)

Click on the Create new entry button at the top right. This opens a new form to enter the user’s details.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tge45xgulfza9qkjibxz.png)

Enter the user’s username, email, and password. Once you’re done, click on the Save button at the top right.

Next, go to Settings → Roles → Authenticated and select all the permissions, and hit save.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l8sybhnxhy3pmsda2m91.png)

## Set up Medusa

To initiate your Medusa server, run the following command:

```bash
medusa new medusa-server --seed
```

The `--seed` flag creates an SQLite database and seeds it with some demo data.

Change to the `medusa-server` directory and go to `medusa.config.js`. Change the exported object at the end to enable Redis:

```jsx
module.exports = {
  projectConfig: {
    redis_url: REDIS_URL,
    //...
  }
  //...
};
```

The default Redis connection string is `redis://localhost:6379` but if you have made changes to it, go to the `.env` file and add the following:

```bash
REDIS_URL=<YOUR_REDIS_URL>
```

Where `<YOUR_REDIS_URL>` is your connection string.

Additionally, since the Remix storefront runs on `localhost:3000`, you have to add an environment variable `STORE_CORS` that sets the URL of the storefront.

Add the following in `.env`:

```tsx
STORE_CORS=http://localhost:3000
```

### Install Strapi Plugin

To install the Strapi plugin, run the following command in your Medusa server’s directory:

```bash
yarn add medusa-plugin-strapi
```

Then, add the following environment variables:

```bash
STRAPI_USER=<STRAPI_IDENTIFIER>
STRAPI_PASSWORD=<STRAPI_PASSWORD>
STRAPI_PROTOCOL=http
STRAPI_URL=<STRAPI_URL> # Optional
STRAPI_PORT=<STRAPI_PORT> # Optional
```

Where:

- `<STRAPI_IDENTIFIER>` is either the email address or username of the user you created in the previous step.
- `<STRAPI_PASSWORD>` is the password of the user you created in the previous step.
- `<STRAPI_PROTOCOL>` is the protocol of your Strapi server. Since, you’re using a local Strapi server, set this to `http`. The default value is `https`.
- `<STRAPI_URL>` is the URL of your Strapi server. By default, the URL is `localhost`.
- `<STRAPI_PORT>` is the port the Strapi server runs on. By default, the port is `1337`.

Finally, open `medusa-config.js` and add the following new item to the `plugins` array:

```jsx
const plugins = [
  //...
  {
    resolve: `medusa-plugin-strapi`,
    options: {
      strapi_medusa_user: process.env.STRAPI_USER,
      strapi_medusa_password: process.env.STRAPI_PASSWORD,
      strapi_url: process.env.STRAPI_URL, //optional
      strapi_port: process.env.STRAPI_PORT, //optional
      strapi_protocol: process.env.STRAPI_PROTOCOL //optional
    }
  }
];
```

### Test Integration

Make sure the Strapi server is still running. If not, you can run the following command to run the Strapi server in the directory of the Strapi project:

```bash
yarn develop
```

Make sure your Redis server is up and running as well.

Then, in the directory of your Medusa server, run the following command to start the Medusa server:

```bash
yarn start
```

This will start your Medusa server on `localhost:9000`. You’ll see that `product.created` events have been triggered along with similar events.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vnimttbpjeg3zkwjqrc8.png)

This will update Strapi with the demo products you seeded.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/utlg6wsyco7cvak8td3o.png)

## Add CMS Pages in Strapi

You will now use Strapi to manage content on your storefront’s homepage. You will be able to control three things from Strapi after this implementation: the hero text that will appear at the top of the storefront; the subheading below the hero text; and the list of products shown on the homepage. 

On your Strapi dashboard, go to Content-Type Builder under Plugins in your Strapi Dashboard. This is where you can define the model/schema for your content.

Click on “Create new single type” under “Single Types”.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7smfgwqexhrbd9ucr1sp.png)

Enter the display name as “Home Page” (if you have used another, you will have to use the appropriate API ID for it later) and hit continue.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8qb86zqz9nto0md4p6oc.png)

Next, select the component field and give it the display name “Hero Text”, and a category homepage (click create “homepage” under the category). Then, click on configure the component.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a66ozfd86h4qspho3ih4.png)

Then give it the name `hero_text` in the next step and click Finish.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9olpu2i671sqjgolq3w3.png)

Go to the Hero Text component under Homepage in components and create three text fields named `start_text`, `mid_text` and `end_text`.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ebbna8oojgox5txntvia.png)

Here, the three text fields have been added because later on in the article you will give a special underline to the `mid_text` to highlight it.

Go back to the Home Page type under single types and add a relation field to products. The relation should be “homepage has many products”. Give it a field name `products_list`.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/14z0ewrwkxw6davhl2rm.png)

Finally, add a text field `heading_2`. Save your changes in the homepage content type. 

This is what your homepage content type should look like:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lr0zqqplg2axvd4529rz.png)

Next, go to Settings → Users & Permissions Plugin → Roles → Public, and enable find permission for the homepage and product type. Hit save.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1a3dky7war2bxv7xarmk.png)

Now, go to the content manager and under the Home Page add your hero text and the products you wish to display under the relations section to the right. Hit save and then publish.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q7pq5k7kfzscip0nwr4i.png)

## Set up the Remix Storefront

In this section, you’ll set up the ecommerce storefront with Remix.

Remix has three official pre-built templates for you to use depending on your needs, but you can also start with a basic one or create your own.

### Set up Remix

To setup a Remix app (do this in a separate directory from `medusa-server` and `strapi-medusa`), run the following command:

```bash
npx create-remix@latest my-storefront
```

It will ask you a few questions. Choose `Just the basics`, then choose your preferred hosting platform (you can choose Remix App Server if you are unsure), choose typescript, and no for `npm install` if you wish to use `yarn`.

Then, change to the `my-storefront` directory and install dependencies with yarn:

```bash
cd my-storefront
yarn install
```

### Configure Tailwind CSS

Install Tailwind CSS to design the UI element:

```tsx
yarn add -D tailwindcss postcss autoprefixer concurrently
```

Run `npx tailwindcss init` to create your `tailwind.config.js` file. Then, set its content to the following:

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

Also, change the scripts in your package.json:

```tsx
{
  "scripts": {
    "build": "npm run build:css && remix build",
    "build:css": "tailwindcss -m -i ./styles/app.css -o app/styles/app.css",
    "dev": "concurrently \"npm run dev:css\" \"remix dev\"",
    "dev:css": "tailwindcss -w -i ./styles/app.css -o app/styles/app.css"
  }
}
```

Then, create the file `styles/app.css` with the following content:

```tsx
@tailwind base;
@tailwind components;
@tailwind utilities;
```

Lastly, add this to your `app/root.tsx` after the list of imports:

```tsx
import styles from "./styles/app.css"

export function links() {
  return [{ rel: "stylesheet", href: styles }]
}
```

You can now use Tailwind CSS in your app.

### Connect Storefront to Medusa Server

Once this is done let’s connect your storefront to your Medusa server.

First, you need to install a few packages with the following command:

```bash
yarn add medusa-react react-query @medusajs/medusa
```

The `medusa-react` library uses [react-query](https://react-query.tanstack.com/overview) as a solution for server-side state management and lists the library as a peer dependency.

In order to use the hooks exposed by `medusa-react`, you will need to include the `MedusaProvider` somewhere up in your component tree. The `MedusaProvider` takes a `baseUrl` prop which should point to your Medusa server. Under the hood, `medusa-react` uses the `medusa-js` client library (built on top of `axios`) to interact with your server.

In addition, because medusa-react is built on top of react-query, you can pass an object representing react-query's [QueryClientProvider](https://react-query.tanstack.com/reference/QueryClientProvider#_top) props, which `MedusaProvider` will pass along.

You also need to wrap your app in a `CartProvider` since that will let you use the cart functionalities provided by Medusa, which you will do later.

Create a file `app/lib/config.ts`. This file will contain your `medusaClient` which will let you use Medusa’s Javascript client in your app.

```tsx
import Medusa from '@medusajs/medusa-js';
import { QueryClient } from 'react-query';

const MEDUSA_BACKEND_URL = 'http://localhost:9000';

const STRAPI_API_URL = 'http://127.0.0.1:1337/api';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      refetchOnWindowFocus: false,
      staleTime: 1000 * 60 * 60 * 24,
      retry: 1,
    },
  },
});

const medusaClient = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 });

export { MEDUSA_BACKEND_URL, STRAPI_API_URL, queryClient, medusaClient };
```

Now go to your `app/root.tsx` and import the required packages:

```tsx
import { MedusaProvider, CartProvider } from 'medusa-react';
import { MEDUSA_BACKEND_URL, queryClient } from './lib/config';
```

You can also edit the `meta` here to change your metadata

```tsx
export const meta: MetaFunction = () => ({
  charset: 'utf-8',
  title: 'New Remix App',
  viewport: 'width=device-width,initial-scale=1',
});
```

Below this, you will see the `App` component. In the returned JSX add the `MedusaProvider` and `CartProvider` with some base styles to the `body`:

```tsx
return (
    <html lang="en">
      <head>
        <Meta />
        <Links />
      </head>
      <body className="bg-black text-slate-400 overflow-x-hidden justify-center flex">
        <MedusaProvider
          queryClientProviderProps={{ client: queryClient }}
          baseUrl={MEDUSA_BACKEND_URL}
        >
					<CartProvider>
	          <Outlet />
	          <ScrollRestoration />
	          <Scripts />
	          <LiveReload />
					</CartProvider>
        </MedusaProvider>
      </body>
    </html>
  );
```

### Display Home Page from Strapi

The data for your home page is available on the Strapi endpoint: `localhost:1337/api/home-page` (add `?populate=*` to also show the nested products). It returns an object with the `data` & `meta` arrays. You don't have to care about the `meta`, what you should really care about is `data`, it contains all the content you entered in your Strapi Dashboard.

First, create the file `app/types/StrapiResponse.ts` with the following content:

```tsx
// StrapiResponse.ts

export type StrapiResponseType = {
  data: {
    id: number;
    attributes: {
      createdAt: Date;
      updatedAt: Date;
      publishedAt: Date;
      hero_text: {
        id: number;
        start_text: string;
        mid_text: string;
        end_text: string;
      };
      products_list: {
        data: Array<StrapiProductData>;
      };
      heading_2: string;
    };
  };
  meta: {};
};

export type StrapiProductData = {
  id: number;
  attributes: {
    medusa_id: string;
    title: string;
    subtitle: string | null;
    description: string;
    handle: string;
    is_giftcard: boolean;
    status: 'draft' | 'proposed' | 'published' | 'rejected';
    thumbnail: string;
    discountable: boolean;
    weight: number;
    product_length: null;
    width: null;
    height: null;
    hs_code: null;
    origin_country: null;
    mid_code: null;
    material: string | null;
    createdAt: Date;
    updatedAt: Date;
  };
};
```

This is the format in which your data is returned from the Strapi API.

Next, create a utility function to fetch your content from the Strapi API. Create a file `app/models/home.server.ts` with the following content:

```tsx
// home.server.ts

import { STRAPI_API_URL } from "~/lib/config";
import type {
  StrapiProductData,
  StrapiResponseType,
} from "~/types/StrapiResponse";

export const getHomePageData = async () => {
  const homePage: StrapiResponseType = await (
    await fetch(`${STRAPI_API_URL}/home-page?populate=*`)
  ).json();

  const { data } = homePage;

  const { attributes } = data;

  const heroText = attributes.hero_text;
  const products = attributes.products_list.data;
  const smallHeading = attributes.heading_2;

  const homePageData = { heroText, products, smallHeading };
  return homePageData;
};

export type homePageDataType = {
  heroText: {
    id: number;
    start_text: string;
    mid_text: string;
    end_text: string;
  };
  products: StrapiProductData[];
  smallHeading: string;
};
```

In the `getHomePageData` function, you should only return the data you need on your home page.

In the above code sample, you will notice that in the import statement `~` is used, this is because it is the alias set for the `app` directory set in the `tsconfig` by default in Remix, if you wish you can change it at your convenience.

All files inside the `app/routes` directory will be a route. For example, `app/routes/store.tsx` will contain the `/store` route.

Next, go to `app/routes/index.tsx` and create a loader function:

```tsx
import { getHomePageData, homePageDataType } from '~/models/home.server';

export const loader = async () => {
  const homePageData = await getHomePageData();

  return homePageData;
};
```

To use the response you received from the loader function you will use the `useLoaderData` hook from Remix inside the `Index` component:

```tsx
import { useLoaderData } from '@remix-run/react';

export default function Index() {
  const { heroText, products, smallHeading } =
    useLoaderData<homePageDataType>();

	...
}
```

Here, `homePageData` was destructured and brought in using `useLoaderData`, now you can use it on your page.

Then, change the returned JSX to the following:

```tsx
export default function Index() {
	//...

  return (
    <div className="px-10 sm:px-20 md:px-44 pt-44 max-w-[100rem] flex-grow w-screen">
      {/* Hero Section */}
      <div>
        <h1 className="text-[2.5rem] sm:text-5xl lg:text-6xl xl:text-8xl relative font-medium lg:leading-[1.15] xl:leading-[1.2]">
          {heroText.start_text}{' '}
          {heroText.mid_text.split(' ').map((text) => (
            <span key={text} className="text-gray-50">
              <span className="relative">
                {text}
                <div className="h-1 bg-emerald-200 w-full absolute bottom-0 left-0 inline-block" />
              </span>{' '}
            </span>
          ))}
          {heroText.end_text}
        </h1>
      </div>
    </div>
  );
}
```

`heroText.start_text` brings data from the `start_text` text field under the `hero_text` component you made in Strapi. Similarly, `heroText.mid_text` and `heroText.end_text` are from `mid_text` and `end_text` fields from Strapi respectively.

Then `mid_text` has been split so that each word gets a uniform underline in case there are multiple words, you will see it happen a bit later in the homepage UI.

To display your products, create the file `app/components/productCard.tsx` with the following content:

```tsx
import { Link } from '@remix-run/react';

interface ProductCardType {
  image: string;
  title: string;
  handle: string;
}

export default function ProductCard({ image, title, handle }: ProductCardType) {
  return (
    <Link to={`/products/${handle}`}>
      <div className="flex flex-col space-y-1 p-2 hover:bg-slate-400 hover:bg-opacity-25 cursor-pointer active:scale-95 transition ease-in-out duration-75">
        <img src={image} alt="" />
        <h3 className="pt-2 text-white text-xl">{title}</h3>
      </div>
    </Link>
  );
}
```

The `Link` comes from Remix and will help you redirect to the products page. The handle prop which is available in Medusa products will be used as a slug.

Now getting back to your `app/routes/index.tsx`, you will map your Strapi response (products) to the page.

Do this just below your hero section:

```tsx
import ProductCard from '~/components/productCard';

export default function Index() {
	...

	return (
		<div className="px-10 sm:px-20 md:px-44 pt-44 max-w-[100rem] flex-grow w-screen">
			...
	
			<div className="flex flex-col items-center pt-40 pb-44">
		    <h2 className="text-2xl sm:text-3xl lg:text-4xl pb-10 text-white">
		      {smallHeading}
	      </h2>
	      <div className="grid grid-cols-2 xl:grid-cols-4 gap-x-6">
		      {products.map(({ attributes }) => (
		        <ProductCard
		          key={attributes.medusa_id}
	            image={attributes.thumbnail}
	            handle={attributes.handle}
	            title={attributes.title}
	          />
	        ))}
	      </div>
		  </div>
		</div>
)
```

### Test Homepage

To test out your homepage, start your Remix development server with `yarn dev` (make sure that your Medusa and Strapi servers are already running).

Your app is ready at `localhost:3000` and it will look like the following: 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u9ca7w25svgc8jnwt0yd.png)

### Implement Add to Cart Functionality with Medusa

To add your products to the cart, you first need to associate a cart with the customer. To do this, you can create a wrapper around your app that checks if a cart has already been initialized or need to be created, and does the needful. 

Create the file `app/components/outletContainer.tsx` with the following content:

```tsx
import { useCart } from 'medusa-react';
import { ReactNode, useEffect } from 'react';

import { medusaClient } from '~/lib/config';

interface OutletContainerType {
  children: ReactNode;
}

export default function OutletContainer({ children }: OutletContainerType) {
  const { setCart } = useCart();

  useEffect(() => {
    const localCartId = localStorage.getItem('cart_id');
    localCartId
      ? medusaClient.carts.retrieve(localCartId).then(({ cart }) => {
          setCart(cart);
        })
      : medusaClient.carts.create().then(({ cart }) => {
          localStorage.setItem('cart_id', cart.id);
          setCart(cart);
        });
  }, []);

  return <div>{children}</div>;
}
```

You are using `medusa-react`'s `useCart` hook, `setCart` will set your cart globally. You can then use it anywhere in your app. The `outletContainer` will also save cart localStorage so that the added items persist even when the user returns.

You will also need to show toast notifications when a product is added to the cart. Install `react-hot-toast` to do this:

```tsx
yarn add react-hot-toast
```

Now, go back to your `app/root.tsx` and wrap your `<Outlet />` with `OutletContainer`. Also, add `<Toaster />` from `react-hot-toast` that will let you show notifications:

```tsx
import OutletContainer from './components/outletContainer';
import { Toaster } from 'react-hot-toast';

export default function App() {
  return (
    ...
	    <CartProvider>
	      <OutletContainer>
	        <Outlet />
        </OutletContainer>
        ...
				<Toaster />
      </CartProvider>
	  ...
  );
}
```

### Create Product Page

In this section, you’ll create a product page. When you are deploying to production, you can’t make a separate page for each of your products, so you will create a dynamic page that will run according to your product’s `handle`. In Remix you will name your dynamic pages as `$slug.tsx`.

You will need to get the `handle` from the URL of your page, you can do that with a loader function but it’s much simpler to use `useParams` hook.

Create the file `app/routes/products/$slug.tsx` with the following content:

```tsx
import { useParams } from '@remix-run/react';
import { useCart, useCreateLineItem, useProducts } from 'medusa-react';

export default function ProductSlug() {
  const { slug } = useParams();
}
```

`slug` is getting your page’s slug from your URL, for example, in `localhost:3000/products/sweatshirt` the slug is `sweatshirt` (remember you passed in the `handle` in your `ProductCard` component).

Next, fetch your product from Medusa using the `useProducts` hook and add it to the UI:

```tsx
export default function ProductSlug() {
  ...

  const { products } = useProducts(
    {
      handle: slug,
    },
    {}
  );

if (!products) {
    return <div></div>; // you can use skeleton loader here instead.
  }

const product = products[0];

return (
  <div className="flex flex-col items-center lg:justify-between lg:flex-row px-10 sm:px-20 md:px-44 pt-44 max-w-[100rem] flex-grow w-screen">
    <img src={product.thumbnail!} className="h-96 w-auto" />
    <div>
      <h1 className="text-4xl pb-10 text-white">{product.title}</h1>
      <p className="w-72">{product.description}</p>
    </div>
  </div>
 );
}
```

Here, the `useProducts` hook was used and passed the slug. While the product is being loaded you show an empty div (you can use a skeleton loader instead).

Finally, you use the first item returned by the `useProducts` hook which is the product that has the handle in the page’s URL.

Please notice that the title and description are used here from the Medusa server since the Strapi plugin supports two-way sync. So, whenever you make changes to the products in Strapi, they’re reflected on the Medusa server as well. You can alternatively show the CMS data for the product from Strapi instead.

You also need to show the prices for your customers according to their region. To do this, create the file `app/lib/formatPrice.ts`:

```tsx
import { formatVariantPrice } from 'medusa-react';
import type { Cart } from "medusa-react/dist/types";
import type { ProductVariant } from '@medusajs/medusa';

export const formatPrice = (variant: ProductVariant, cart: Cart) => {
  if (cart)
    return formatVariantPrice({
      variant: variant,
      region: cart.region,
    });
};
```

You use the `formatVariantPrice` function here from `medusa-react`. This formats the price according to your user’s region and the product variant selected.

Then, use it in `app/routes/products/$slug.tsx`:

```tsx
import { formatPrice } from '~/lib/formatPrice';

export default ProductSlug() {
	...

	const { cart } = useCart();

	return (
		<div className="flex flex-col items-center lg:justify-between lg:flex-row px-10 pb-44 sm:px-20 md:px-44 pt-44 max-w-[100rem] flex-grow w-screen">
      <img src={product.thumbnail!} className="h-96 w-auto" />
      <div>
        <h1 className="text-4xl pt-5 lg:pt-0 pb-5 lg:pb-10 text-white">
          {product.title}
        </h1>
        <p className="w-72">{product.description}</p>
        <p className="text-xl text-white pt-5">
          {formatPrice(product.variants[0], cart)}
        </p>
      </div>
    </div>
	)
}
```

Next, create a function to add to the cart and push notifications.

```tsx
import toast from 'react-hot-toast';

export default function ProductSlug() {
	...	

  const { mutate } = useCreateLineItem(cart?.id!);

  const addItem = () => {
    mutate(
      {
        variant_id: products?.slice(0, 1)[0].variants[0].id!,
        quantity: 1,
      },
      {
        onSuccess: () => {
          toast('Added to Cart!');
        },
      }
    );
  };

	...
}
```

The `useCreateLineItem` hook lets you add items. It requires a cart ID. The `addItem` function will add the product to the cart and then show a toast notification.

Add the button that will run this function on click in the returned JSX:

```tsx
export default function ProductSlug() {
  ...

  return (
    <div className="flex flex-col items-center lg:justify-between lg:flex-row px-10 sm:px-20 md:px-44 pt-44 max-w-[100rem] flex-grow w-screen">
      <img src={product.thumbnail!} className="h-96 w-auto" />
      <div>
        <h1 className="text-4xl pb-10 text-white">{product.title}</h1>
        <p className="w-72">{product.description}</p>
				<p className="text-xl text-white pt-5">
          {formatPrice(product.variants[0])}
        </p>
        <button
          className="p-5 rounded-md w-full bg-slate-400 bg-opacity-25 mt-10 cursor-pointer active:scale-95 transition ease-in-out duration-75"
          onClick={() => addItem()}
        >
          Add item
        </button>
      </div>
    </div>
  );
}
```

The last step is to add a navigation bar to make it easy to navigate to the cart.

Create the file `app/components/topNavigator.tsx` with the following content:

```tsx
import { Link } from '@remix-run/react';

export default function TopNavigator() {
  return (
    <nav className="flex w-screen fixed top-0 right-0 left-0 items-center py-4 flex-row justify-between px-10 sm:px-20 md:px-44 z-10 bg-black">
      <Link to="/" className="text-xl">
        MRS
      </Link>
      <Link to="/cart">Cart</Link>
    </nav>
  );
}
```

Add the `TopNavigator` component to your `root.tsx` so it appears on all pages. Add it just above the `Outlet`:

```tsx
import TopNavigator from './components/topNavigator';

export default function App() {
  return (
    ...
	    <CartProvider>
	      <OutletContainer>
					<TopNavigator />
	        <Outlet />
        </OutletContainer>
        ...
      </CartProvider>
	  ...
  );
}
```

### Test Product Page

To test out your product page, restart your Remix server (make sure Strapi and Medusa servers are already running).

Click on any of the products on your homepage and you will be able to see the details.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k8xqk8670ubodovi528g.png)

### Create Cart Page

Now, you will create your very final cart page.

Create the file `app/routes/cart.tsx` with the following content:

```tsx
import { useState, useEffect } from "react";
import { medusaClient } from "~/lib/config";

import type { Cart as CartType } from "medusa-react/dist/types";

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

  useEffect(() => {
    medusaClient.carts
      .retrieve(localStorage.getItem("cart_id")!)
      .then(({ cart }) => {
        setCart(cart);
      });
  }, [cart]);

  return (
    <div className="px-10 sm:px-20 md:px-44 pt-44 max-w-[100rem] flex-grow w-screen">
      {cart?.items.map((variant) => (
        <div
          key={variant.id}
          className="flex flex-col xl:flex-row h-64 my-10 space-x-8 space-y-4 items-center"
        >
          <img className="h-full" src={variant.thumbnail!} />
          <div>
            <h3 className="pt-2 text-white text-xl">{variant.title}</h3>
            <p className="text-slate-400">{variant.quantity}</p>
          </div>
        </div>
      ))}
    </div>
  );
}
```

`cart.items` is an array of all the items in the customer’s cart. You display each item with its thumbnail, title, and quantity.

### Test Cart Page

Restart your Remix server (make sure Strapi and Medusa servers are already running). When you add an item to the cart it will show on the cart page.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ha8hpofbxnf6j752ib8.png)

## Conclusion

By following along with this tutorial, you can see how easy it is to use fully open source tools to build an ecommerce store.

There’s still much more that can be done to improve your storefront such as:

- Improve the UI
- [Improve Cart](https://docs.medusajs.com/guides/carts-in-medusa/) page to add functionalities such as removing or updating items.
- [Implement Checkout Flow](https://docs.medusajs.com/advanced/storefront/how-to-implement-checkout-flow/)
- Integrate [Stripe Plugin](https://docs.medusajs.com/add-plugins/stripe) for payment.
- [Install Medusa Admin](https://docs.medusajs.com/admin/quickstart) to manage orders, products, and much more.
- Check out [Medusa’s Storefront API](https://docs.medusajs.com/api/store/) Reference to learn what more you can do in your storefront.

> Should you have any issues or questions related to Medusa, then feel free to reach out to the Medusa team via [Discord](https://discord.gg/F87eGuwkTp).