Step 32 of 36 (89% complete)

Combining Product Data from Shopify and Optimizely on the PDP

Szymon Uryga photo

In this implementation, we merge commerce data from Shopify with content data from Optimizely CMS to build a rich and performant Product Detail Page (PDP).

We use the product’s handle to retrieve its details from Shopify, then fetch additional content from Optimizely using the product's SKU as a key. The two data sources are merged into a unified structure used to render the final PDP.

Step 1: Get Product Data from Shopify

export async function getShopifyProduct(
  handle: string
): Promise<ShopifyProductMapped | undefined> {
  const productPath = handle.replace('/product/', '')
  const res = await shopifyFetch<ShopifyProductOperation>({
    query: getProductQuery,
    tags: [TAGS.products],
    variables: {
      handle: productPath,
    },
  })

  return reshapeProduct(res.body.data.product, false)
}

const reshapeProduct = (
  product: ShopifyProduct,
  filterHiddenProducts: boolean = true
) => {
  if (
    !product ||
    (filterHiddenProducts && product.tags.includes(HIDDEN_PRODUCT_TAG))
  ) {
    return undefined
  }

  const { images, variants, ...rest } = product

  return {
    ...rest,
    images: reshapeImages(images, product.title),
    variants: removeEdgesAndNodes(variants),
  }
}


export const getProductQuery = /* GraphQL */ `
  query getProduct($handle: String!) {
    product(handle: $handle) {
      ...product
    }
  }
  ${productFragment}
`

We query Shopify using GraphQL to fetch the product by its handle and reshape the response into our internal format.

Step 2: Get Content Data from Optimizely (CMS)

export const getOptimizelyFullProducts = async (
  productsSKU: string[],
  locale: string
): Promise<OptimizelyProduct[]> => {
  const locales = getValidLocale(locale)
  const response = await optimizely.getProductsBySKUS({
    locales,
    skus: productsSKU,
  })

  if (response.errors || !response.data?.Product?.items) {
    return []
  }

  return response.data.Product.items.map((item) => ({
    description: item?.Description?.html ?? null,
    gallery: item?.Gallery ?? [],
    sku: item?.SKU ?? null,
    thumbnail: item?.Thumbnail ?? null,
    product_name: item?.product_name ?? null,
    title: item?.Title ?? null,
    short_description: item?.short_description ?? null,
    keywords: item?.Keywords ?? null,
  }))
}

GraphQL query used to get content from Optimizely based on SKU:

query getProductBySKU($locales: [Locales], $sku: String) {
  Product(locale: $locales, where: { SKU: { eq: $sku } }) {
    item {
      Description {
        html
      }
      Gallery {
        __typename
        ...CommonImageFragment
      }
      SKU
      Thumbnail
      product_name
      Title
      short_description
      Keywords
    }
  }
}

We query Optimizely CMS for rich content such as product descriptions, SEO metadata, and image galleries. This is done using the product SKU as a reference.

Step 3: Merge Both Sources

export const getProductDetailedInfo = async (
  productUrl: string,
  locale: string
): Promise<ProductWithDetails | null> => {
  const productData = await getShopifyProduct(productUrl)
  if (!productData) {
    return null
  }

  const productInfo = await getOptimizelyFullProducts(
    [productData.sku],
    locale
  )

  return mapShopifyProductWithDetails(productData, productInfo?.[0])
}

The main method combines both data sets by:

  • Fetching commerce data from Shopify

  • Fetching content data from Optimizely

  • Mapping the results into a unified format

Data Mapper

export const mapShopifyProductWithDetails = (
  product: ShopifyProductMapped,
  productInfo?: OptimizelyProduct
): ProductWithDetails | null => {
  if (!productInfo || !product.variants?.[0]) {
    return null
  }

  const productCompact = mapShopifyProductCompact(
    product,
    product.variants?.[0],
    productInfo
  )

  const isSimpleProduct = product.variants.length === 1
  const options = !isSimpleProduct ? product.options : []

  return {
    ...productCompact,
    ...{
      isSimpleProduct,
      options,
      gallery: mapProductGallery(productInfo),
      variants: mapShopifyProductVariants(
        product,
        product.variants,
        productInfo
      ),
      details: {
        ...productCompact.details,
      },
      seo: {
        title: productInfo.title,
        description: productInfo.short_description,
        keywords: productInfo.keywords,
      },
    },
  } as ProductWithDetails
}

This function merges product variants, gallery images, content descriptions, and SEO fields into a single ProductWithDetails object used on the frontend.

Final Output

Visual Integration Example

CMS elements (highlighted with blue borders):

  • Product images
  • Product title
  • Product description

Commerce platform elements (highlighted with green borders):

  • Price information
  • Product options
  • Add-to-cart functionality using productId and variantId

Have questions? I'm here to help!

Contact Me