DEV Community

Cover image for Shopify + Next.js + Tailwind CSS: Modern eCommerce
iskurbanov
iskurbanov

Posted on • Edited on

Shopify + Next.js + Tailwind CSS: Modern eCommerce

Article brought to you by buildnextshop.com

In the last couple years, we have all gotten extremely comfortable with online shopping. This has pushed e-commerce brands to invest heavily in their online shopping experience. Shopify merchants have seen record growth in 2020 and 2021, driving the demand for Shopify developers. Many Shopify merchants are moving away from themes and starting to venture into headless e-commerce solutions.

In this article I will be showing you the basics of setting up a headless e-commerce store using Shopify GraphQL Storefront API with Next.js as the frontend framework and tailwind CSS for styling.

Next.js allows us to create blazing fast stores using static site generation, which is a perfect fit for e-commerce.

Let's get started:

1. Setup Next.js Project with Tailwind CSS

There are many ways to set up a Next.js project but for our project this one is the fastest.

npx create-next-app -e with-tailwindcss build-next-shop
cd build-next-shop
Enter fullscreen mode Exit fullscreen mode

2. Set up Shopify Partners account and create a new store

Navigate to Shopify Partners and create your Shopify Partners account (if you don't have one already).

Then, navigate to the Stores tab on the left-hand side and create a new store.

Among other benefits, a Partners account will allow you to easily manage stores and have unlimited time to work on them before transferring to a client.

3. Connect Shopify store to Next.js app

Navigate to the Apps tab in your store and hit the "Manage private apps" link at the bottom:

Shopify manage private apps

Accept the terms and conditions, name your private app "Next.js Connection" and enter your email address. Then scroll down and check "Allow this app to access your storefront data using using the Storefront API".

Storefront API Shopify

Create .env.local file in the root folder of your Next.js app and add these variables:

SHOPIFY_STOREFRONT_ACCESSTOKEN='storefront api access token'
SHOPIFY_STORE_DOMAIN='yourstore.myshopify.com'
Enter fullscreen mode Exit fullscreen mode

(not all files/folders shown)

build-next-shop
 ┣ node_modules
 ┣ pages
 ┣ public
 ┣ .env.local *
 ┗ package.json
....
Enter fullscreen mode Exit fullscreen mode

4. Install Shopify GraphiQL App

Navigate to Shopify GraphiQL App, scroll to the bottom, hit "Select all" scopes for Storefront API access, and install the Shopify GraphiQL app that will help you to test your queries before you use them in your app.

(Before proceeding with this step, add some sample products to your store and make sure they are available for the GraphiQL App sales channel).

Adding Products

Now open the GraphiQL app, choose the Storefront API schema and paste this into the query field:

{
  products(first:5) {
    edges {
      node {
        id
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Shopify GraphiQL app

Congrats! You made your first Storefront API query!

5. Fetching Products in Next.js Storefront

Inside your Next.js app, create a lib folder in the root directory and create a shopify.js file inside of it.

(not all files/folders shown)

build-next-shop
 ┣ lib
 ┃ ┗ shopify.js *
 ┣ node_modules
 ┣ pages
 ┣ public
 ┣ .env.local
 ┗ package.json
....
Enter fullscreen mode Exit fullscreen mode

Fill out shopify.js:

const domain = process.env.SHOPIFY_STORE_DOMAIN
const storefrontAccessToken = process.env.SHOPIFY_STOREFRONT_ACCESSTOKEN

async function ShopifyData(query) {
  const URL = `https://${domain}/api/2021-07/graphql.json`

  const options = {
    endpoint: URL,
    method: "POST",
    headers: {
      "X-Shopify-Storefront-Access-Token": storefrontAccessToken,
      "Accept": "application/json",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ query })
  }

  try {
    const data = await fetch(URL, options).then(response => {
      return response.json()
    })

    return data
  } catch (error) {
    throw new Error("Products not fetched")
  }
}

export async function getAllProducts() {
  const query = `
  {
  products(first: 25) {
    edges {
      node {
        id
        title
        handle
        priceRange {
          minVariantPrice {
            amount
          }
        }
        images(first: 5) {
          edges {
            node {
              originalSrc
              altText
            }
          }
        }
      }
    }
  }
}
`

  const response = await ShopifyData(query)

  const allProducts = response.data.products.edges ? response.data.products.edges : []

  return allProducts
}
Enter fullscreen mode Exit fullscreen mode

What we are doing here:

  1. Creating a function called ShopifyData that will accept a query.
  2. ShopifyData it will make a POST request to the Shopify Storefront GraphQL API using the set headers and return the json response.
  3. ShopifyData function will return the data to the getAllProducts function which will set it equal to the allProducts variable.

6. Displaying Products on homepage

Inside your index.js file:

import { getAllProducts } from "../lib/shopify"

export default function Home({ products }) {

  return (
    <div className="bg-white">
      <div className="max-w-2xl mx-auto py-16 px-4 sm:py-24 sm:px-6 lg:max-w-7xl lg:px-8">
        <h2 className="text-2xl font-extrabold text-gray-900 mb-6">
          Products
        </h2>
        <div className="grid grid-cols-1 gap-y-10 gap-x-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
         {
            products.map(product => (
              <ProductCard key={product.node.id} product={product} />
            ))
          }
       </div>
      </div>
    </div>
  )
}

export async function getStaticProps() {
  const products = await getAllProducts()

  return {
    props: { products }, // will be passed to the page component as props
  }
}
Enter fullscreen mode Exit fullscreen mode

Create a ProductCard.js component in a new folder called components:

build-next-shop
 ┣ components
 ┃ ┗ ProductCard.js *
 ┣ lib
 ┣ node_modules
 ┣ pages
 ┣ public
 ┣ .env.local
 ┗ package.json
....
Enter fullscreen mode Exit fullscreen mode

Then inside of ProductCard.js

import Link from 'next/link'
import Image from 'next/image'
import { formatter } from '../utils/helpers'

const ProductCard = ({ product }) => {
  const { handle, title } = product.node

  const { altText, originalSrc } = product.node.images.edges[0].node

  const price = product.node.priceRange.minVariantPrice.amount

  return (
    <Link
      href={`/products/${handle}`}
    >
      <a className="group">
        <div className="w-full bg-gray-200 rounded-3xl overflow-hidden">
          <div className="relative group-hover:opacity-75 h-72">
            <Image 
              src={originalSrc}
              alt={altText}
              layout="fill"
              objectFit="cover"
            />
          </div>
        </div>
        <h3 className="mt-4 text-lg font-medium text-gray-900">{title}</h3>
        <p className="mt-1 text-sm text-gray-700">{formatter.format(price)}</p>
      </a>
    </Link>
  )
}

export default ProductCard
Enter fullscreen mode Exit fullscreen mode

What's happening here:

  1. We are using the getStaticProps function provided to us by Next.js to prefetch all the products from the getAllProducts function from our shopify.js file.

  2. Passing the products as a prop to our homepage function.

  3. Creating a ProductCard.js to display a single product card.

  4. Mapping over the products and displaying the ProductCard.js for each one.

Congratulations! You just created your basic Shopify Next.js Storefront.

Next Steps:

  1. Dynamic product pages using the getStaticPaths Next.js function.
  2. Create add to cart functionality using React Context for cart state management.
  3. Deploy to Vercel.

Sample Starter Project: https://github.com/iskurbanov/shopify-next.js-tailwind

Checkout the example website and full tutorial at BuildNextShop.com where we create a fully production ready Shopify Headless store using Next.js!

Top comments (30)

Collapse
 
benbalderas profile image
Ben Balderas

@iskurbanov is there a way to pass a custom option/variant to a certain product, and display it in the cart and checkout? Something like a textfield for an engraving, gift message, etc?

Any pointing in the right direction would be greatly appreciated!

Collapse
 
iskurbanov profile image
iskurbanov

Hey Ben, you can achieve that using the "Custom Attributes" when you are creating the checkout URL. Here is the reference: shopify.dev/api/storefront/referen...

Let me know if that helps!

Collapse
 
benbalderas profile image
Ben Balderas

@iskurbanov Thanks! I think it does help.

Are these "customAttributes" per product? or per cart?

If I purchase your course, does it come with this type of support? 🙂 I'm a bit new to these parts

Thread Thread
 
iskurbanov profile image
iskurbanov

You would add the customAttributes per line item into your cart/checkout.

Definitely! I think after the course you would have a good idea on how to create custom option/variants for your products. We go in-depth with using GraphQL to interact with the Storefront API.

Thread Thread
 
benbalderas profile image
Ben Balderas

I was hoping it would be as easy as adding customAttributes here:

```mutation {
  checkoutCreate(input: {
    lineItems: [{ variantId: "${id}", quantity: ${quantity}, customAttributes: ${customAttributes}}],
  }) {
    checkout {
      id
      webUrl
    }
  }
}```
Enter fullscreen mode Exit fullscreen mode

And passing this as an object with a value from a textfield in the UI:

customAttributes: [{ name: 'Name', value: 'David' }]

But this doesn't seem to work

Thread Thread
 
iskurbanov profile image
iskurbanov • Edited

Hey Ben,

Almost! It has to be a key-value pair, so something like this:

mutation {
  checkoutCreate(input: {
    lineItems: [
      { 
        variantId: "VARIANT_ID",
        quantity: 1
      }
    ],
    customAttributes: [
      {
        key: "Name",
        value: "David"
      }
    ]
  }) {
    checkout {
       id
       webUrl
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

If you click on the [AttributeInput!] it tells you the format: shopify.dev/api/storefront/referen...

Let me know if that helps!

Thread Thread
 
prebenwulff profile image
PrebenWulff

Great! But, now where do I see the custom attributes? I cant see them on the order page.

Great course btw!

Thread Thread
 
iskurbanov profile image
iskurbanov

You should see them on your Shopify admin dashboard under orders.

Thread Thread
 
Sloan, the sloth mascot
Comment deleted
 
prebenww profile image
Preben Wulff

nvm, I figured it out.

Collapse
 
mahdisoultana profile image
Mahdi

thank you very much for this please update your field schema some are deprecated

Collapse
 
chrisb007 profile image
Chris B

Thanks for the tutorial. Very much appreciated. After following this step-by-step, I somehow continue to get this error:

FetchError: request to https://https//spnsors.myshopify.com/api/2022-04/graphql.json failed, reason: getaddrinfo ENOTFOUND https
    at ClientRequest.<anonymous>
    at ClientRequest.emit (node:events:365:28)
    at ClientRequest.emit (node:domain:470:12)
    at TLSSocket.socketErrorListener (node:_http_client:447:9)
    at TLSSocket.emit (node:events:365:28)
    at TLSSocket.emit (node:domain:470:12)
    at emitErrorNT (node:internal/streams/destroy:193:8)
    at emitErrorCloseNT (node:internal/streams/destroy:158:3)
    at processTicksAndRejections (node:internal/process/task_queues:83:21) {
  type: 'system',
  errno: 'ENOTFOUND',
  code: 'ENOTFOUND'
}
wait  - compiling /_error (client and server)...
wait  - compiling...
event - compiled client and server successfully in 124 ms (570 modules)
error - utils/fetchShop.js (26:10) @ productData
Error: Products not fetched
  24 |   } catch (error) {
  25 |     console.log(error);
> 26 |     throw new Error('Products not fetched');
     |          ^
  27 |   }
  28 | }
Enter fullscreen mode Exit fullscreen mode

Super new to shopify, how can I debug this?

Collapse
 
iskurbanov profile image
iskurbanov

Hi Chris, seems like there might be an issue with your env file. Double check to see if your values are set up properly. You can also try to input your env values directly into your code to see if that resolves the issue.

Collapse
 
franadev profile image
Fran Agulto

Thanks so much @iskurbanov , I am a Junior level React dev and this was super helpful to get me started. I will take the entire tutorial. Your content is super clear and you explain things very well. I appreciate it!

Collapse
 
fernandodomain profile image
fernandodomain • Edited

I am getting this error :(
dev-to-uploads.s3.amazonaws.com/up...

Collapse
 
iskurbanov profile image
iskurbanov

Could you push your code to github so I can take a look?

Collapse
 
fernandodomain profile image
fernandodomain
Thread Thread
 
iskurbanov profile image
iskurbanov

Shows a 404, maybe it a private repo?

Thread Thread
 
fernandodomain profile image
fernandodomain

Sorry. Made it public

Thread Thread
 
iskurbanov profile image
iskurbanov

Try to add a next.config.js file to the root directory like this: github.com/iskurbanov/shopify-next...

Your next.js might have issues accessing your env file (also I would suggest not pushing that on your public repo in case you have some sensitive information there, luckily the Storefront API is readonly).

Collapse
 
dramoslace profile image
Deiby Dayans R

Do you have some idea how to update the Shopify APP boilerplate with Next 12 version?

Collapse
 
iskurbanov profile image
iskurbanov

You can try changing the next.js version in package.json and then run the npm install command. I would personally wait a bit before implementing Next.js 12 on any major projects.

Collapse
 
x751685875 profile image
许永恒

Do you have a course catalogue?

Collapse
 
iskurbanov profile image
iskurbanov

Hi, if you’re referring to the outline, it’s available at buildnextshop.com. Let me know if that helps!

Collapse
 
danshawl profile image
Dan Shawl

Should this all still work with shopify deprecating private apps? Theres only custom apps now.

Collapse
 
iskurbanov profile image
iskurbanov

Hi Dan, yes it's the same thing, just different name.

Collapse
 
grivas97 profile image
grivas97

for some one reason im only pulling in 1 product?

Collapse
 
grivas97 profile image
grivas97 • Edited

for some reason i couldnt get the updated index.js to work but the this one in the tutorial worked for me

Collapse
 
okanserbest profile image
okanserbest

I made such a design for fashion, you can examine it.

github.com/okanserbest/Shopify-Nex...

Collapse
 
iskurbanov profile image
iskurbanov

Looks great!