Skip to main content

Yotpo UGC Component

The Yotpo UGC Carousel displays user-generated photos from Yotpo as a horizontally scrollable carousel with a modal gallery. It supports both homepage (album-based) and PDP (product-based, style/fabric-filtered) usage.


Responsibilitiesโ€‹

  • Fetch UGC images from the appโ€™s /api/yotpoUgc/:album endpoint
  • Enrich tagged products via server-side product lookups
  • Filter images by product style and fabric on PDPs
  • Render a Swiper carousel with responsive breakpoints and navigation
  • Open a modal gallery for a selected image
  • Emit analytics events for scroll, modal open, and click interactions (PDP vs homepage)

Data Flowโ€‹

1. Client โ†’ App APIโ€‹

On mount (or when productDetails change), the component builds a URL to the internal Yotpo API route:

const url = new URL(`${window.location.origin}${APIRoutes.YOTPO_UGC}${DEFAULT_ALBUM}`);

if (productId) {
url.searchParams.append('isPdp', 'true');
url.searchParams.append('productId', productId.toString());
}
  • DEFAULT_ALBUM is "Primary".
  • When productId is present, the request is treated as a PDP request (isPdp=true) so the server fetches the product-level album rather than a named album.
  • The request is made via axios.get(url.href) inside a guarded useEffect (using an isCancelled flag) to avoid updating state after unmount.

2. App API โ†’ Yotpoโ€‹

The Remix loader at routes/api/yotpoUgc receives the request and calls:

withCache.run(CacheStrategy.slower, () => fetchYotpoPhotoAlbum(...))

Caching is keyed by request.url.

If an error occurs, the loader returns a JSON payload with an error key rather than throwing.


3. Yotpo โ†’ Enriched Dataโ€‹

fetchYotpoPhotoAlbum builds the appropriate Yotpo API URL.

PDP (product-based):

https://api.yotpo.com/v1/widget/${YOTPO_APP_KEY}/albums/product/${getByProduct.productId}

Album (name-based):

https://api.yotpo.com/v1/widget/${YOTPO_APP_KEY}/albums/by_name?album_name=${ALBUM_NAME}&page=${pageNumber || 1}&per_page=50

The function then:

  1. Fetches Yotpoโ€™s response via axios
  2. Iterates over data.images
  3. For each image.tagged_products, extracts the Shopify handle from product.product_url and calls getProductByHandle
  4. Replaces tagged_products with the resolved product data (excluding null)
  5. Filters out any image that:
    • is not media_type === "image"
    • has zero resolved tagged_products

If an error occurs, it logs via captureIssue and returns null.


PDP Style/Fabric Filteringโ€‹

For PDP usage, productDetails may include style and fabric.
The client further filters enriched images by matching their YGroup tag:

YGroup_{style}_{fabric}

(YGroup tags come from Storefront product enrichment.)


Analyticsโ€‹

The component uses useAnalytics from Hydrogen to publish distinct events for PDP vs non-PDP usage.

  • PDP: CustomAnalyticsEvent.YOTPO_CAROUSEL_SCROLL_PDP
  • Non-PDP: CustomAnalyticsEvent.YOTPO_CAROUSEL_SCROLL
  • PDP: CustomAnalyticsEvent.YOTPO_MODAL_OPEN_PDP
  • Non-PDP: CustomAnalyticsEvent.YOTPO_MODAL_OPEN

Product Card Click (Shop By Look)โ€‹

  • PDP: CustomAnalyticsEvent.YOTPO_UGC_PRODUCT_CARD_CLICK_PDP
  • Non-PDP: CustomAnalyticsEvent.YOTPO_UGC_PRODUCT_CARD_CLICK

Color Bubble Click (Shop By Color)โ€‹

  • PDP: CustomAnalyticsEvent.YOTPO_COLOR_BUBBLE_CLICK_PDP
  • Non-PDP: CustomAnalyticsEvent.YOTPO_COLOR_BUBBLE_CLICK

These events allow downstream analytics to distinguish UGC interactions between PDP and homepage placements.
Each event fires uniquely and should not fire together.


Propsโ€‹

YotpoUgcCarouselProps includes:

  • title: string โ€” Section heading
  • subtitle?: string โ€” Optional subheading (processed through capitalizeSentences)
  • marginBottom?: boolean โ€” Adds bottom margin (mb-8 md:mb-[60px])
  • productDetails?: { id: number | string style?: { value: string } fabric?: { value: string }}

When productDetails are omitted, the component behaves as a generic album carousel (e.g., homepage).
When provided, it fetches PDP-specific album data and applies style/fabric-based filtering.


Usageโ€‹

Homepage (Album-Based)โ€‹

<YotpoUgcCarousel
title="Styled by You"
subtitle="Real customers wearing our dresses"
marginBottom
/>

PDP (Product-Based)โ€‹

<YotpoUgcCarousel
title="Styled With This Dress"
subtitle="See how customers wear this style"
marginBottom
productDetails={{
id: product.id,
style: product.style,
fabric: product.fabric,
}}
/>