How to Dynamically updated Next.js Static Pages with SWR
Tags: react, nextjs, shopify, swr
Published: 2022-02-18 01:21:51

 *Article brought to you by [buildnextshop.com](https://www.buildnextshop.com)*

This is a continuation to the [Shopify + Next.js + Tailwind CSS](https://dev.to/iskurbanov/shopify-next-js-tailwind-css-modern-ecommerce-4475) article that I wrote in September 2021. It already has over 7000 views!

If you have been using React, you probably already know that Next.js is a pretty sweet framework that provides a bunch of features on top of React. Some of the popular ones include: SSR (Server-side Rendering), SSG (Static-site Generation), simplified dynamic routing, easy deployment with Vercel, and much more. 

In this article I would like to introduce you to [SWR (stale-while-revalidate)](https://swr.vercel.app/) package that is also created by the Vercel team. 

SWR allows us to add CSR (Client-side rendering) to our static pages generated by Next.js.

So why would we want to add SWR? Well, SSG pages give us a great speed advantage, which is super important in e-commerce. But it also has a disadvantage that we need to rebuild and redeploy any changes that happen to the static pages. This becomes a problem when we want to update small components of our app. 

I think this example will give you a good understanding of the power of SWR so let's dive right in!

To follow along with this example, you will need to: 
- set up a Shopify private app (you can refer to this [youtube tutorial on our channel] (https://youtu.be/RgGItagzlGo))
- clone this [Github repository](https://github.com/iskurbanov/shopify-next.js-tailwind)

### 1. Setting up our Next.js /api/available.js file
The /api folder in Next.js is a bit like magic. It allows us to build an API endpoint right in our frontend application. They are server-side only bundles and won't increase your client-side bundle size.

```
build-next-shop
 ┣ lib
 ┣ node_modules
 ┣ pages
 ┃ ┗ api 
 ┃   ┗ hello.js *
 ┣ public
 ┣ .env.local
 ┗ package.json
....

```

Let's remove everything in the `hello.js` file, rename it to `available.js` and paste in this code:

```
export default async function available(req, res) {
  const { query: { id } } = req

  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")
    }
  }

  async function getProduct(handle) {
    const query = `
  {
    productByHandle(handle: "${handle}") {
      id
      variants(first: 25) {
        edges {
          node {
            id
            availableForSale
          }
        }
      }
    }
  }`

    const response = await ShopifyData(query)

    const product = response.data.productByHandle ? response.data.productByHandle : []

    return product
  }

  const products = await getProduct(id)

  res.status(200)
  res.json(products)
}
```

So what is happening in this code?

1. We are creating and exporting and async function `available` with two parameters: request and response. 

2. We are deconstructing the `req.query.id` variable to get the `id`

3. Grabbing our secret values from our .env file and assigning them to domain and storefrontAccessToken variables.

4. Setting an options variable that contains our Shopify Graphql URL, method of request, headers, and our query body. 

5. We created a `getProduct` function that receives a handle (which we called id in our case). 

6. We save the results of our getProduct function into a `products` variable. 

7. We return the products variable in json format to our Next.js component that calls the API. 


### 2. Updating our ProductForm.js component

```
build-next-shop
 ┣ .next
 ┣ components
 ...
 ┃ ┗ ProductForm.js 
 ┣ context
 ┣ lib
 ┣ node_modules
 ┗ pages
....

```

Let's `import useSWR from "swr"` and `import axios from "axios"` add our custom fetcher function at the top of the component


```
import useSWR from "swr"
import axios from "axios"

const fetcher = (url, id) => (
  axios.get(url, {
    params: {
      id: id
    }
  }).then((res) => res.data)
)
```

This function replaces the default swr fetcher function and replaces it with axios ([read more about it here](https://swr.vercel.app/docs/data-fetching))

Then we will use the useSWR hook inside of our component:

```
...
export default function ProductForm({ product }) {

  const { data: productInventory } = useSWR(
    ['/api/available', product.handle],
    (url, id) => fetcher(url, id),
    { errorRetryCount: 3 }
  )
...
// rest of the component not shown
```

Now we can `console.log(productInventory)` and grab the data from our API! 


### 3. Add a useEffect hook to update our state

Let's add a new state:

```
  const [available, setAvailable] = useState(true)
```

and then we can update it with our useEffect hook like this:

```
 useEffect(() => {
    if (productInventory) {
      const checkAvailable = productInventory?.variants.edges.filter(item => item.node.id === selectedVariant.id)

      if (checkAvailable[0].node.availableForSale) {
        setAvailable(true)
      } else {
        setAvailable(false)
      }
    }
  }, [productInventory, selectedVariant])`
```

First we are checking if `productInventory` was fetched. Then we find the variant that is selected and search for it in our `productInventory` variable and update our button state based on the result. 


### 4. Update the button UI based on the availability like this:


```
return (
    <div className="rounded-2xl p-4 shadow-lg flex flex-col w-full md:w-1/3">
      <h2 className="text-2xl font-bold">{product.title}</h2>
      <span className="pb-3">{formatter.format(product.variants.edges[0].node.priceV2.amount)}</span>
      {
        product.options.map(({ name, values }) => (
          <ProductOptions
            key={`key-${name}`}
            name={name}
            values={values}
            selectedOptions={selectedOptions}
            setOptions={setOptions}
          />
        ))
      }
      {
        available ?
          <button
            onClick={() => {
              addToCart(selectedVariant)
            }}
            className="bg-black rounded-lg text-white px-2 py-3 mt-3 hover:bg-gray-800">
            Add To Card
          </button>
          :
          <button
            className="rounded-lg text-white px-2 py-3 mt-3 bg-gray-800 cursor-not-allowed">
            Sold out!
          </button>
      }
    </div>
  )
```

Here we create a ternary to check our available state and choose which button to display based on the boolean value. 


I hope you enjoyed the tutorial! 

Sample Starter Project: [https://github.com/iskurbanov/shopify-next.js-tailwind](https://github.com/iskurbanov/shopify-next.js-tailwind)

Checkout the example website and full tutorial at [BuildNextShop.com](https://www.buildnextshop.com) where we create a fully production ready Shopify Headless store using Next.js!