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/:albumendpoint - Enrich tagged products via server-side product lookups
- Filter images by product
styleandfabricon 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
productIdis 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 guardeduseEffect(using anisCancelledflag) 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:
- Fetches Yotpoโs response via axios
- Iterates over
data.images - For each
image.tagged_products, extracts the Shopify handle fromproduct.product_urland callsgetProductByHandle - Replaces
tagged_productswith the resolved product data (excludingnull) - Filters out any image that:
- is not
media_type === "image" - has zero resolved
tagged_products
- is not
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.
Carousel Scrollโ
- PDP:
CustomAnalyticsEvent.YOTPO_CAROUSEL_SCROLL_PDP - Non-PDP:
CustomAnalyticsEvent.YOTPO_CAROUSEL_SCROLL
Modal Openโ
- 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,
}}
/>