diff --git a/app/page.tsx b/app/page.tsx
index 0fad0ac28..7d407ede8 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -9,7 +9,7 @@ export const metadata = {
   }
 };
 
-export default async function HomePage() {
+export default function HomePage() {
   return (
     <>
       <ThreeItemGrid />
diff --git a/app/product/[handle]/page.tsx b/app/product/[handle]/page.tsx
index dd964ccf5..e2280675d 100644
--- a/app/product/[handle]/page.tsx
+++ b/app/product/[handle]/page.tsx
@@ -4,6 +4,7 @@ import { notFound } from 'next/navigation';
 import { GridTileImage } from 'components/grid/tile';
 import Footer from 'components/layout/footer';
 import { Gallery } from 'components/product/gallery';
+import { ProductProvider } from 'components/product/product-context';
 import { ProductDescription } from 'components/product/product-description';
 import { HIDDEN_PRODUCT_TAG } from 'lib/constants';
 import { getProduct, getProductRecommendations } from 'lib/shopify';
@@ -72,7 +73,7 @@ export default async function ProductPage({ params }: { params: { handle: string
   };
 
   return (
-    <>
+    <ProductProvider>
       <script
         type="application/ld+json"
         dangerouslySetInnerHTML={{
@@ -97,13 +98,15 @@ export default async function ProductPage({ params }: { params: { handle: string
           </div>
 
           <div className="basis-full lg:basis-2/6">
-            <ProductDescription product={product} />
+            <Suspense fallback={null}>
+              <ProductDescription product={product} />
+            </Suspense>
           </div>
         </div>
         <RelatedProducts id={product.id} />
       </div>
       <Footer />
-    </>
+    </ProductProvider>
   );
 }
 
diff --git a/app/search/loading.tsx b/app/search/loading.tsx
index 855c371bc..7b75dd922 100644
--- a/app/search/loading.tsx
+++ b/app/search/loading.tsx
@@ -7,7 +7,7 @@ export default function Loading() {
         .fill(0)
         .map((_, index) => {
           return (
-            <Grid.Item key={index} className="animate-pulse bg-neutral-100 dark:bg-neutral-900" />
+            <Grid.Item key={index} className="animate-pulse bg-neutral-100 dark:bg-neutral-800" />
           );
         })}
     </Grid>
diff --git a/components/cart/add-to-cart.tsx b/components/cart/add-to-cart.tsx
index 4f7cb31b3..5a260af2b 100644
--- a/components/cart/add-to-cart.tsx
+++ b/components/cart/add-to-cart.tsx
@@ -3,7 +3,7 @@
 import { PlusIcon } from '@heroicons/react/24/outline';
 import clsx from 'clsx';
 import { addItem } from 'components/cart/actions';
-import { useProductOptions } from 'components/product/product-context';
+import { useProduct } from 'components/product/product-context';
 import { Product, ProductVariant } from 'lib/shopify/types';
 import { useFormState } from 'react-dom';
 import { useCart } from './cart-context';
@@ -60,13 +60,11 @@ function SubmitButton({
 export function AddToCart({ product }: { product: Product }) {
   const { variants, availableForSale } = product;
   const { addCartItem } = useCart();
-  const { options: selectedOptions } = useProductOptions();
+  const { state } = useProduct();
   const [message, formAction] = useFormState(addItem, null);
 
   const variant = variants.find((variant: ProductVariant) =>
-    variant.selectedOptions.every(
-      (option) => option.value === selectedOptions[option.name.toLowerCase()]
-    )
+    variant.selectedOptions.every((option) => option.value === state[option.name.toLowerCase()])
   );
   const defaultVariantId = variants.length === 1 ? variants[0]?.id : undefined;
   const selectedVariantId = variant?.id || defaultVariantId;
diff --git a/components/layout/navbar/search.tsx b/components/layout/navbar/search.tsx
index 551d781c2..10286c0a5 100644
--- a/components/layout/navbar/search.tsx
+++ b/components/layout/navbar/search.tsx
@@ -33,7 +33,7 @@ export default function Search() {
         placeholder="Search for products..."
         autoComplete="off"
         defaultValue={searchParams?.get('q') || ''}
-        className="w-full rounded-lg border bg-white px-4 py-2 text-sm text-black placeholder:text-neutral-500 dark:border-neutral-800 dark:bg-transparent dark:text-white dark:placeholder:text-neutral-400"
+        className="text-md w-full rounded-lg border bg-white px-4 py-2 text-black placeholder:text-neutral-500 md:text-sm dark:border-neutral-800 dark:bg-transparent dark:text-white dark:placeholder:text-neutral-400"
       />
       <div className="absolute right-0 top-0 mr-3 flex h-full items-center">
         <MagnifyingGlassIcon className="h-4" />
diff --git a/components/product/gallery.tsx b/components/product/gallery.tsx
index 0b03557a5..f54d6015c 100644
--- a/components/product/gallery.tsx
+++ b/components/product/gallery.tsx
@@ -2,26 +2,15 @@
 
 import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/24/outline';
 import { GridTileImage } from 'components/grid/tile';
-import { createUrl } from 'lib/utils';
+import { useProduct } from 'components/product/product-context';
 import Image from 'next/image';
-import Link from 'next/link';
-import { usePathname, useSearchParams } from 'next/navigation';
 
 export function Gallery({ images }: { images: { src: string; altText: string }[] }) {
-  const pathname = usePathname();
-  const searchParams = useSearchParams();
-  const imageSearchParam = searchParams.get('image');
-  const imageIndex = imageSearchParam ? parseInt(imageSearchParam) : 0;
+  const { state, updateImage } = useProduct();
+  const imageIndex = state.image ? parseInt(state.image) : 0;
 
-  const nextSearchParams = new URLSearchParams(searchParams.toString());
   const nextImageIndex = imageIndex + 1 < images.length ? imageIndex + 1 : 0;
-  nextSearchParams.set('image', nextImageIndex.toString());
-  const nextUrl = createUrl(pathname, nextSearchParams);
-
-  const previousSearchParams = new URLSearchParams(searchParams.toString());
   const previousImageIndex = imageIndex === 0 ? images.length - 1 : imageIndex - 1;
-  previousSearchParams.set('image', previousImageIndex.toString());
-  const previousUrl = createUrl(pathname, previousSearchParams);
 
   const buttonClassName =
     'h-full px-6 transition-all ease-in-out hover:scale-110 hover:text-black dark:hover:text-white flex items-center justify-center';
@@ -43,23 +32,21 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
         {images.length > 1 ? (
           <div className="absolute bottom-[15%] flex w-full justify-center">
             <div className="mx-auto flex h-11 items-center rounded-full border border-white bg-neutral-50/80 text-neutral-500 backdrop-blur dark:border-black dark:bg-neutral-900/80">
-              <Link
+              <button
                 aria-label="Previous product image"
-                href={previousUrl}
+                onClick={() => updateImage(previousImageIndex.toString())}
                 className={buttonClassName}
-                scroll={false}
               >
                 <ArrowLeftIcon className="h-5" />
-              </Link>
+              </button>
               <div className="mx-1 h-6 w-px bg-neutral-500"></div>
-              <Link
+              <button
                 aria-label="Next product image"
-                href={nextUrl}
+                onClick={() => updateImage(nextImageIndex.toString())}
                 className={buttonClassName}
-                scroll={false}
               >
                 <ArrowRightIcon className="h-5" />
-              </Link>
+              </button>
             </div>
           </div>
         ) : null}
@@ -69,17 +56,13 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
         <ul className="my-12 flex items-center justify-center gap-2 overflow-auto py-1 lg:mb-0">
           {images.map((image, index) => {
             const isActive = index === imageIndex;
-            const imageSearchParams = new URLSearchParams(searchParams.toString());
-
-            imageSearchParams.set('image', index.toString());
 
             return (
               <li key={image.src} className="h-20 w-20">
-                <Link
-                  aria-label="Enlarge product image"
-                  href={createUrl(pathname, imageSearchParams)}
-                  scroll={false}
+                <button
+                  aria-label="Select product image"
                   className="h-full w-full"
+                  onClick={() => updateImage(index.toString())}
                 >
                   <GridTileImage
                     alt={image.altText}
@@ -88,7 +71,7 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
                     height={80}
                     active={isActive}
                   />
-                </Link>
+                </button>
               </li>
             );
           })}
diff --git a/components/product/product-context.tsx b/components/product/product-context.tsx
index 6809822af..7be633b26 100644
--- a/components/product/product-context.tsx
+++ b/components/product/product-context.tsx
@@ -3,71 +3,70 @@
 import { useRouter, useSearchParams } from 'next/navigation';
 import React, { createContext, useContext, useMemo, useOptimistic } from 'react';
 
-type ProductOptionsState = {
+type ProductState = {
   [key: string]: string;
+} & {
+  image?: string;
 };
 
-type ProductOptionsAction = { type: 'UPDATE_OPTION'; payload: { name: string; value: string } };
-
-type ProductOptionsContextType = {
-  options: ProductOptionsState;
+type ProductContextType = {
+  state: ProductState;
   updateOption: (name: string, value: string) => void;
+  updateImage: (index: string) => void;
 };
 
-const ProductOptionsContext = createContext<ProductOptionsContextType | undefined>(undefined);
+const ProductContext = createContext<ProductContextType | undefined>(undefined);
 
-function productOptionsReducer(
-  state: ProductOptionsState,
-  action: ProductOptionsAction
-): ProductOptionsState {
-  switch (action.type) {
-    case 'UPDATE_OPTION': {
-      return {
-        ...state,
-        [action.payload.name]: action.payload.value
-      };
-    }
-    default:
-      return state;
-  }
-}
-
-export function ProductOptionsProvider({ children }: { children: React.ReactNode }) {
+export function ProductProvider({ children }: { children: React.ReactNode }) {
   const router = useRouter();
   const searchParams = useSearchParams();
 
-  const getInitialOptions = () => {
-    const params: ProductOptionsState = {};
+  const getInitialState = () => {
+    const params: ProductState = {};
     for (const [key, value] of searchParams.entries()) {
       params[key] = value;
     }
     return params;
   };
 
-  const [options, updateOptions] = useOptimistic(getInitialOptions(), productOptionsReducer);
+  const [state, setOptimisticState] = useOptimistic(
+    getInitialState(),
+    (prevState: ProductState, update: ProductState) => ({
+      ...prevState,
+      ...update
+    })
+  );
 
   const updateOption = (name: string, value: string) => {
-    updateOptions({ type: 'UPDATE_OPTION', payload: { name, value } });
+    setOptimisticState({ [name]: value });
     const newParams = new URLSearchParams(window.location.search);
     newParams.set(name, value);
     router.push(`?${newParams.toString()}`, { scroll: false });
   };
 
+  const updateImage = (index: string) => {
+    setOptimisticState({ image: index });
+    const newParams = new URLSearchParams(window.location.search);
+    newParams.set('image', index);
+    router.push(`?${newParams.toString()}`, { scroll: false });
+  };
+
   const value = useMemo(
     () => ({
-      options,
-      updateOption
+      state,
+      updateOption,
+      updateImage
     }),
-    [options]
+    [state]
   );
 
-  return <ProductOptionsContext.Provider value={value}>{children}</ProductOptionsContext.Provider>;
+  return <ProductContext.Provider value={value}>{children}</ProductContext.Provider>;
 }
 
-export function useProductOptions() {
-  const context = useContext(ProductOptionsContext);
+export function useProduct() {
+  const context = useContext(ProductContext);
   if (context === undefined) {
-    throw new Error('useProductOptions must be used within a ProductOptionsProvider');
+    throw new Error('useProduct must be used within a ProductProvider');
   }
   return context;
 }
diff --git a/components/product/product-description.tsx b/components/product/product-description.tsx
index b2e3dbfea..427916a84 100644
--- a/components/product/product-description.tsx
+++ b/components/product/product-description.tsx
@@ -1,33 +1,29 @@
 import { AddToCart } from 'components/cart/add-to-cart';
 import Price from 'components/price';
-import { ProductOptionsProvider } from 'components/product/product-context';
 import Prose from 'components/prose';
 import { Product } from 'lib/shopify/types';
-import { Suspense } from 'react';
 import { VariantSelector } from './variant-selector';
 
 export function ProductDescription({ product }: { product: Product }) {
   return (
-    <Suspense fallback={null}>
-      <ProductOptionsProvider>
-        <div className="mb-6 flex flex-col border-b pb-6 dark:border-neutral-700">
-          <h1 className="mb-2 text-5xl font-medium">{product.title}</h1>
-          <div className="mr-auto w-auto rounded-full bg-blue-600 p-2 text-sm text-white">
-            <Price
-              amount={product.priceRange.maxVariantPrice.amount}
-              currencyCode={product.priceRange.maxVariantPrice.currencyCode}
-            />
-          </div>
-        </div>
-        <VariantSelector options={product.options} variants={product.variants} />
-        {product.descriptionHtml ? (
-          <Prose
-            className="mb-6 text-sm leading-tight dark:text-white/[60%]"
-            html={product.descriptionHtml}
+    <>
+      <div className="mb-6 flex flex-col border-b pb-6 dark:border-neutral-700">
+        <h1 className="mb-2 text-5xl font-medium">{product.title}</h1>
+        <div className="mr-auto w-auto rounded-full bg-blue-600 p-2 text-sm text-white">
+          <Price
+            amount={product.priceRange.maxVariantPrice.amount}
+            currencyCode={product.priceRange.maxVariantPrice.currencyCode}
           />
-        ) : null}
-        <AddToCart product={product} />
-      </ProductOptionsProvider>
-    </Suspense>
+        </div>
+      </div>
+      <VariantSelector options={product.options} variants={product.variants} />
+      {product.descriptionHtml ? (
+        <Prose
+          className="mb-6 text-sm leading-tight dark:text-white/[60%]"
+          html={product.descriptionHtml}
+        />
+      ) : null}
+      <AddToCart product={product} />
+    </>
   );
 }
diff --git a/components/product/variant-selector.tsx b/components/product/variant-selector.tsx
index a36ca7cba..c89f99a81 100644
--- a/components/product/variant-selector.tsx
+++ b/components/product/variant-selector.tsx
@@ -1,7 +1,7 @@
 'use client';
 
 import clsx from 'clsx';
-import { useProductOptions } from 'components/product/product-context';
+import { useProduct } from 'components/product/product-context';
 import { ProductOption, ProductVariant } from 'lib/shopify/types';
 
 type Combination = {
@@ -17,7 +17,7 @@ export function VariantSelector({
   options: ProductOption[];
   variants: ProductVariant[];
 }) {
-  const { options: selectedOptions, updateOption } = useProductOptions();
+  const { state, updateOption } = useProduct();
   const hasNoOptionsOrJustOneOption =
     !options.length || (options.length === 1 && options[0]?.values.length === 1);
 
@@ -42,7 +42,7 @@ export function VariantSelector({
           const optionNameLowerCase = option.name.toLowerCase();
 
           // Base option params on current selectedOptions so we can preserve any other param state.
-          const optionParams = { ...selectedOptions, [optionNameLowerCase]: value };
+          const optionParams = { ...state, [optionNameLowerCase]: value };
 
           // Filter out invalid options and check if the option combination is available for sale.
           const filtered = Object.entries(optionParams).filter(([key, value]) =>
@@ -57,7 +57,7 @@ export function VariantSelector({
           );
 
           // The option is active if it's in the selected options.
-          const isActive = selectedOptions[optionNameLowerCase] === value;
+          const isActive = state[optionNameLowerCase] === value;
 
           return (
             <button