Step 32 of 36 (89% complete)
Combining Product Data from Shopify and Optimizely on the PDP
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
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!