How to build an e-commerce storefront with Next.js and Shopify

What is the Shopify Storefront API?

The Shopify Storefront API allows your custom frontend to talk with Shopify’s backend. You can make API calls to Shopify and access product data and other services like the checkout page.
Reading this article, you will learn how to build a Shopify storefront using the Storefront API and Next.js. By the end, you should be able to send GraphQL requests to Shopify and use the response data in your front end.
Let’s get started!

Creating a Shopify store

Navigate to Shopify Partners and create your Shopify Partner account (if you don't have one already). The account will allow you to create a store for development purposes at no cost.
Click Stores on the Shopify Partners dashboard to add a store. Ensure you select Development Store as the store type, then fill in the store details.
Once you create the store, you are ready to create a private app.

Creating a Shopify Private App


Create a private app by following the steps below:

  1. From your Shopify admin, click Settings > Apps and sales channels.
  2. Click Develop apps.
  3. Click Create an app.
  4. In the modal window, enter the App name.
  5. Click Create app.

Once you’ve created the app, follow these steps to configure the Storefront API.

  1. Navigate to the configuration tab and click Storefront API integration.
  2. Select all the checkboxes in the Storefront API access scopes. It permits you to fetch products.
  3. Click Save.

To get the access token:

  1. Click Install app on the app’s dashboard.
  2. In the modal, click Install app.
  3. Copy the access token from the API Credentials tab.

Copy the access token and the shop URL in the .env file.

The shop URL follows this format.

https://{store_name}.myshopify.com/api/2022-10/graphql.json

The last step of setting up Shopify is to add sample products to the store.

Creating a Next.js project

Start by running the following command in the terminal to create a Next.js project.

npx create-next-app@latest

This command should prompt you as follows:
√ What is your project named? ... nextjs-shopify

√ Would you like to use TypeScript with this project? ... No / Yes

√ Would you like to use ESLint with this project? ... No / Yes

Name the project shopify-store and select “No” for TypeScript and “Yes” for EsLint.

Now, run the app using the following command.

npm run dev

Querying the Storefront API

To make GraphQL requests, use graphql-request, a minimal client for sending GraphQL requests.

Run the following command on the terminal to install it.

npm install graphql-request

To fetch the products from Shopify and manage the cart, you’ll need queries that do the following:

  • fetch all products
  • fetch a single product
  • add items to the cart
  • update the cart
  • retrieve the checkout URL

Let’s create these queries.

In the base folder of your app, create a new folder called utils and add a new file named shopify.js.

Fetching All Products from Shopify

Open the utils/shopify.js file in your editor and add the code which imports GraphQLClient and gql from graphql-request.

import { gql, GraphQLClient } from "graphql-request";

Assign the Storefront access token and the shop URL to a storefrontAccessToken and endpoint variable.

const storefrontAccessToken = process.env.STOREFRONTACCESSTOKEN;

const endpoint = process.env.SHOPURL

Add the following code to create a GraphQL client instance. You’ll use it to make requests.

const graphQLClient = new GraphQLClient(endpoint, {
    headers: {
      "X-Shopify-Storefront-Access-Token": storefrontAccessToken,
    },
  });

Create a new function called getProducts and define the query for fetching products from Shopify.

export async function getProducts() {
  const getAllProductsQuery = gql`
    {
      products(first: 10) {
        edges {
          node {
            id
            title
            handle
            priceRange {
              minVariantPrice {
                amount
              }
            }
            featuredImage {
              altText
              url
            }
          }
        }
      }
    }
  `;
}
Then, use the query to make the API request.
export async function getProducts() {
  const getAllProductsQuery = gql`
   // Omitted for brevity
  `;
  try {
    return await graphQLClient.request(getAllProductsQuery);
  } catch (error) {
    throw new Error(error);
  }
}

You can now call the getProducts function to fetch products from Shopify. Next, you’ll create a function that fetches a single product by handle.

Fetch a product from Shopify

In utils/shopify.js, add a new function called getProduct that accepts a product ID as an argument.

export const getProduct = async (id) => {

};

Modify the function to include the query for fetching the product.

export const getProduct = async (id) => {
  const productQuery = gql`
    query getProduct($id: ID!) {
      product(id: $id) {
        id
        handle
        title
        description
        priceRange {
          minVariantPrice {
            amount
            currencyCode
          }
        }
        featuredImage {
          url
          altText
        }
        variants(first: 10) {
          edges {
            node {
              id
            }
          }
        }
      }
    }
  `;
  };

In getProduct, use the query in the GraphQL request and pass the ID as a variable.

export const getProduct = async (id) => {
  const productQuery = gql`
  // Omitted for brevity
  `;
  const variables = {
    id,
  };
  try {
    const data = await queryShopify(productQuery, variables);
    return data.product;
  } catch (error) {
    throw new Error(error);
  }

};

Next, you’ll create a function that creates a cart and adds items to the cart.

Adding an item to the cart

In utils/shopify.js, add a new function named addToCart, that accepts a product ID and quantity as arguments.
Next, define the mutation for creating a cart.

export async function addToCart(itemId, quantity) {
  const createCartMutation = gql`
  mutation createCart($cartInput: CartInput) {
    cartCreate(input: $cartInput) {
      cart {
        id
      }
    }
  }
`;
}

Use the mutation to create a cart.
export async function addToCart(itemId, quantity) {
  const createCartMutation = gql`
    // Omitted for brevity
  `
  const variables = {
    cartInput: {
      lines: [
        {
          quantity: parseInt(quantity),
          merchandiseId: itemId,
        },
      ],
    },
  };
  try {
    return await graphQLClient.request(createCartMutation, variables);
  } catch (error) {
    throw new Error(error);
  }
}

This function creates a new cart and adds a product to it. Next, you’ll create a function that adds a product to an existing cart.

Updating the cart

Add a function named updateCart in utils/shopify.js and define the mutation for updating the cart.

export async function updateCart(cartId, itemId, quantity) {
  const updateCartMutation = gql`
    mutation cartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
      cartLinesAdd(cartId: $cartId, lines: $lines) {
        cart {
          id
        }
      }
    }
  `;
}

This mutation is used to find the cart with the specified cart ID and update it with the item.
Modify the updateCart function to make the request as follows:

export async function updateCart(cartId, itemId, quantity) {
  const updateCartMutation = gql`
   // Omitted for brevity
    }
  `;
  const variables = {
    cartId: cartId,
    lines:  [
    {
      quantity: parseInt(quantity),
      merchandiseId: itemId,
    },
  ];
  };
  try {
   return await graphQLClient.request(updateCartMutation, variables);
  } catch (error) {
    throw new Error(error);
  }
}

After creating or updating the cart, you’ll need to retrieve it

Retrieving the cart

In utils/shopify, add a function named retrieveCart and add a query for retrieving the cart.

export async function retrieveCart(cartId) {
  const cartQuery = gql`
    query cartQuery($cartId: ID!) {
      cart(id: $cartId) {
        id
        createdAt
        updatedAt

        lines(first: 10) {
          edges {
            node {
              id
              quantity
              merchandise {
                ... on ProductVariant {
                  id
                }
              }
            }
          }
        }
        estimatedCost {
          totalAmount {
            amount
          }
        }
      }
    }
  `;
}

This query uses the cart ID to query for IDs of the products in the cart and the total estimated cost. Add the following to the retrieveCart function to make the request.

export async function retrieveCart(cartId) {
  const cartQuery = gql`
    // Omitted for brevity
  `;
  const variables = {
    cartId,
  };
  try {
    const data = await graphQLClient.request(cartQuery, variables);
    return data.cart;
  } catch (error) {
    throw new Error(error);
  }
}

Next, you’ll create a function that retrieves the checkout URL.

Retrieving the checkout URL

Add a function named retrieveCheckout that receives a cart ID as an argument in utils/shopify.js. Then, add the query for fetching the checkout URL from Shopify.

export const getCheckoutUrl = async (cartId) => {
  const getCheckoutUrlQuery = gql`
    query checkoutURL($cartId: ID!) {
      cart(id: $cartId) {
        checkoutUrl
      }
    }
  `;
  const variables = {
    cartId: cartId,
  };


};

Add the following try/catch statement to make the request and return the checkout URL.

export const getCheckoutUrl = async (cartId) => {
  const getCheckoutUrlQuery = gql`
    query checkoutURL($cartId: ID!) {
      cart(id: $cartId) {
        checkoutUrl
      }
    }
  `;
  const variables = {
    cartId: cartId,
  };
  try {
    return await graphQLClient.request(getCheckoutUrlQuery, variables);
  } catch (error) {
    throw new Error(error);
  }
};

You have already created all the functions you’ll use to get products and manage the cart. The next step is to create the frontend, beginning with the products page.

Creating the products page

The products page displays all the products and their prices.


To get started, export the getServerSideProps function in pages/index.js and call the getProducts function from utils/shopify.js.

import { getProducts } from "../utils/shopify";
export default function Home({ products }) {
  return (
    // Display products
  );
}
export const getServerSideProps = async () => {
  const data = await getProducts();
  return {
    props: { data }, 
  };
};
};

This function passes the products as props into the page component.
Add the following code to the page component to iterate over and display each product.
import Head from "next/head";
import styles from "../styles/Home.module.css";
import { getProducts } from "../utils/shopify";
import ProductCard from "../components/ProductCard/ProductCard";

export default function Home({ data }) {
  const products = data.products.edges;
  return (
    <>
      <Head>
        <title>Nextjs Shopify</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main>
        <div className={styles.products}>
          {products.map((product) => {
            return <ProductCard key={product.node.id} product={product} />;
          })}
        </div>
      </main>
    </>
  );
}

Because you’re rendering the ProductCard component for each product, you need to create it.


In the base folder, create a new folder called components and add the ProductCard/ProductCard.js file.


Add the following code that renders the product to this file.

import Image from 'next/image'
import Link from 'next/link'
import styles from "./ProductCard.module.css"


export default function ProductCard({product}) {
  return (
   <>
    <div className={styles.card}>
          <div className={styles.image}>
            <Image src={product.node.featuredImage.url} alt={product.node.featuredImage.alttext} fill={true} />
          </div>
        <div className={styles.content}>
          <small>
            <Link href={`products/${product.node.handle}/?id=${product.node.id}`} className={styles.action}>{product.node.title}</Link>
          </small>
          <small>{product.node.priceRange.minVariantPrice.amount}</small>
        </div> 
      </div>
   </>
  )
}

Note that the product title links to a product details page that you’ll create in the next step.
For brevity, all the CSS files are in the GitHub repo.

Creating the product details page

Create products/[handle].js file in the pages folder. The file will fetch the product details by the ID passed in the URL query.

Start by exporting the getServerSideProps function in products/[id].js file and fetch the product by passing context.query.productid to the getProduct function.

import ProductDetails from "../../components/ProductDetails/ProductDetails";
import { getProduct } from "../../utils/shopify";
export default function Product({ product }) {
  return <ProductDetails product={product} />;
}
export const getServerSideProps = async (context) => {
  const { productid} = context.query;
  const product = await getProduct(productid);
  return {
    props: {
      product,
    },
  };
};

The Product page uses a ProductDetails component, so create components/ProductDetails/ProductDetails.js file and add the following:

import Image from "next/image";
import Link from "next/link";
import { useState } from "react";
import styles from "./ProductDetails.module.css";
export default function ProductDetails({ product }) {
  const [quantity, setQuantity] = useState(0);
  const updateQuantity = (e) => {
    setQuantity(e.target.value);
    if (quantity == 0) setCheckout(false);
  };
  return (
    <>
      <div className={styles.container}>
        <div className={styles.image}>
          <Image
            src={product.featuredImage.url}
            alt={product.featuredImage.altText}
            fill={true}
          />
        </div>
        <div className={styles.content}>
          <span>
            <h2>{product.title}</h2>
            <h3>{product.priceRange.minVariantPrice.amount}</h3>
          </span>
          <input
            value={quantity}
            onChange={updateQuantity}
            type="number"
            min={0}
          />
          <button className={styles.cartbtn} onClick={handleAddToCart}>
            Add to Cart
          </button>
        </div>
      </div>
    </>
  );
}

This component displays the product image, its title, and price. It also allows a user to specify the number of items they want to buy.

The “Add to Cart” button calls a function called handleAddToCart when clicked. Implement it in the ProductDetails component as follows.

const handleAddToCart = async () => {
    let cartId = sessionStorage.getItem("cartId");
    if (quantity > 0) {
      if (cartId) {
        await updateCart(cartId, product.variants.edges[0].node.id, quantity);
      } else {
        let data = await addToCart(product.variants.edges[0].node.id, quantity);
        cartId = data.cartCreate.cart.id;
        sessionStorage.setItem("cartId", cartId);
      }
    }
  };

If the quantity of items is greater than 0 but there is no cart in the session, this function should create a cart and add the item to it. If a cart does exist, it should update the cart with the item.


In the next section, you will list the cart items and the total then create a checkout button.

Creating the cart page

In the pages folder, create a file named cart.js and export a getServerSideProps function that calls the retrieveCart function from utils/shopify.js.

import { retrieveCart, getCheckoutUrl } from "../utils/shopify";
export default function Cart({ products, checkoutUrl}) {
  return (
    <div>
	// Display cart items
    </div>
  );
}
export const getServerSideProps = async (context) => {
  const { cartid } = context.query;
  const cart = await retrieveCart(cartid);
  const data = await getCheckoutUrl(cartid);
  const { checkoutUrl } = data.cart;
  return {
    props: {
      cart,
      checkoutUrl,
    },
  };
};

Call the getCheckoutUrl function to get the checkout URL of the cart.
In the Cart component, iterate over the cart items and render them. Additionally, display the total amount and the checkout button.

import Image from "next/image";
import { getCheckoutUrl, retrieveCart } from "../utils/shopify";
import styles from "../styles/Cart.module.css";
export default function Cart({ cart, checkoutUrl }) {
  return (
    <div className={styles.container}>
      <ul role="list-item">
        {cart.lines.edges.map((item) => {
          return (
            <li key={item.node.id}>
              <div>
                <Image
                  src={item.node.merchandise.product.featuredImage.url}
                  alt={item.node.merchandise.product.featuredImage.altText}
                  width={200}
                  height={240}
                />
              </div>
              <div className={styles.content}>
                <h2>{item.node.merchandise.product.title}</h2>
                <p>
                  {
                    item.node.merchandise.product.priceRange.minVariantPrice
                      .amount
                  }
                </p>
                <p>Quantity: {item.node.quantity}</p>
              </div>
            </li>
          );
        })}
      </ul>
      <h2>Total - {cart.estimatedCost.totalAmount.amount}</h2>
      <a href={checkoutUrl}>
        <button>Checkout</button>
      </a>
    </div>
  );
}

You now have a working cart!

What’s next?

So far, you have fetched products from Shopify, created a cart for users to add products to, and enabled checkout.

You can now add more functionalities to your store. For example, you may:

  • add a function that increases or decreases the quantity of items in the cart,
  • add a function for removing items from the cart,
  • login users and add a buyer identity to each cart.

Find the queries and mutations to implement the above functionality and more in the Shopify GraphQL API documentation.

The sample code for the application created in this tutorial can be found in GitHub repository.