Skip to content

feat(product): display product videos on the PDP gallery#3044

Open
BC-AdamWard wants to merge 1 commit into
bigcommerce:canaryfrom
BC-AdamWard:feat/product-videos-pdp
Open

feat(product): display product videos on the PDP gallery#3044
BC-AdamWard wants to merge 1 commit into
bigcommerce:canaryfrom
BC-AdamWard:feat/product-videos-pdp

Conversation

@BC-AdamWard

@BC-AdamWard BC-AdamWard commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

What/Why?

Product videos were effectively unsupported on the Catalyst PDP even though the
Storefront GraphQL API already exposes them on Product.videos. The API returns
only a { title, url } pair (a YouTube watch URL), so two pieces were
missing: the query never requested the field, and there was no player. This PR
adds both and renders videos inline in the existing PDP gallery, alongside the
images. Product videos are YouTube-only (matching BigCommerce's supported
provider).

  • Data: add videos(first: 25) to the PDP product query; flatten to
    [{ url, title }] in the gallery streamable.
  • Player: use lite-youtube-embed
    (a tiny, dependency-free facade) rather than hand-rolling the embed — it shows
    a poster + play button and injects the youtube-nocookie iframe only on click.
    A small getYouTubeId() helper extracts the id from the watch URL the API
    returns. (The custom element is registered client-side only; it renders as
    inert markup during SSR and upgrades on hydration.)
  • Gallery: videos render after the images in the Embla carousel; each
    <lite-youtube> remounts when its slide is deselected, so playback (and audio)
    stops when the shopper navigates away.
  • A11y/i18n: video thumbnails get a ▶ play badge and labelled aria-labels via
    new playVideo / viewVideo keys in messages/en.json (English source
    strings; other locales left to the translation pipeline).
  • Images: allow i.ytimg.com poster thumbnails through next/image.

Testing

Validated against a live store on a real product that has a video attached.

  • pnpm generate — the videos query validates against the live schema.
  • pnpm lint --max-warnings=0, pnpm typecheck, pnpm build — all clean.
  • PDP render: 0 player iframes on initial load; clicking play mounts exactly
    1 iframe at …/embed/<id>?autoplay=1; the optimized poster is served (200,
    image/jpeg); both aria-labels resolve; no console errors.
  • The video thumbnail shows a play badge and carousels the main viewer to the video.

To test locally: point core/.env.local at a store/channel with a product that
has a video, then pnpm install && pnpm generate && pnpm dev and open that PDP.

Migration

None. The change is additive — the existing images array, the image "load
more" pagination, and the review-form image consumer are unchanged. The
ProductGallery videos prop and the gallery streamable's videos field are
both optional.


Note: getYouTubeId() is a pure function and an ideal unit-test target, but
core/ has no unit-test runner today (tests are Playwright e2e) — happy to add
one in a follow-up if a runner is introduced. Scope is YouTube only, matching
BigCommerce's supported product-video provider; non-YouTube URLs are skipped.

@BC-AdamWard BC-AdamWard requested a review from a team as a code owner June 11, 2026 16:07
@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the BigCommerce Platform Team on Vercel.

A member of the Team first needs to authorize it.

@changeset-bot

changeset-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: dfe13a9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@bigcommerce/catalyst-core Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@BC-AdamWard BC-AdamWard force-pushed the feat/product-videos-pdp branch 3 times, most recently from 9d8ed7e to 9d4cf20 Compare June 11, 2026 16:59

// YouTube ids are short alphanumeric tokens; reject anything else so a malformed
// path tail or encoded query material can't leak into a built URL.
const YOUTUBE_ID = /^[\w-]{6,}$/;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a library we could potentially use instead of rolling our own embed logic?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. Quick context first on why some transform is unavoidable, then the library option.

The Storefront API returns a YouTube watch URL, which can't be iframed directly — YouTube blocks /watch in frames, only /embed/<id> is frameable. So inline playback always needs a watch-URL → embed-id step regardless of approach; the real question is whether we own that logic or delegate it.

First-party option: @next/third-parties/google's YouTubeEmbed (it wraps lite-youtube-embed under the hood). It would replace the embed-URL/poster construction here, the click-to-play facade in product-gallery.tsx, and the i.ytimg.com next/image remote pattern — and removes the hand-built iframe src/thumbnail URLs entirely. We'd keep only a small getYouTubeId() parser, since the component takes a videoid and the API gives us a watch URL.

Honestly, neither route is clearly ideal, so this feels like your call on which risk is more acceptable for core:

  • @next/third-parties — first-party and removes most of the custom/security-sensitive URL building, but it's still marked experimental by Next and adds a runtime dependency to vibes/soul.
  • Roll our own (current) — keeps vibes/soul dependency-free, but we maintain (and you review) the parsing/embed logic.
  • Middle ground — depend on lite-youtube-embed directly (stable, not experimental) and wrap the web component ourselves: stable dep, a bit more glue.

A tiny ID parser stays either way. Which trade-off would you prefer — experimental first-party dep, stable third-party dep, or keeping it in-house? Happy to implement whichever you pick.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd go with either @next/third-parties or lite-youtube-embed instead of rolling our own code. I just don't want to roll my own custom video code in Catalyst that we have to maintain.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went with lite-youtube-embed directly. The custom embed/facade is gone — the gallery now renders <lite-youtube>, which owns the poster, click-to-play, and the youtube-nocookie iframe. The only thing left is a small getYouTubeId(), since the Storefront API returns watch URLs and the element needs the bare id.

Chose it over @next/third-parties because that wrapper is still flagged experimental, whereas lite-youtube-embed is stable and zero-dependency (and is what @next/third-parties wraps anyway). It's registered client-side only so SSR doesn't trip on the custom element, and each <lite-youtube> remounts when its carousel slide is deselected so playback stops on navigate-away. Scope is YouTube-only, matching BC product-video support.

Pushed.

@BC-AdamWard BC-AdamWard force-pushed the feat/product-videos-pdp branch from 9d4cf20 to f35766b Compare June 11, 2026 20:00
Fetch Product.videos (title, url) from the Storefront GraphQL API and render
them inline in the PDP gallery alongside images — previously unsupported in
Catalyst despite the data being available. Product videos are YouTube-only,
matching BigCommerce's supported provider.

- use lite-youtube-embed (a tiny, dependency-free facade) for the player: it
  shows a poster + play button and injects the youtube-nocookie iframe only on
  click, so no heavy player loads on PDP view. The custom element is registered
  client-side only and renders as inert markup during SSR.
- getYouTubeId() extracts the video id from the watch URL the API returns
- videos render after the images in the Embla carousel; each <lite-youtube>
  remounts when its slide is deselected so playback stops on navigate-away
- video thumbnails get a play badge and labelled aria-labels via new i18n keys
  playVideo / viewVideo (English source strings; other locales via the
  translation pipeline)
- allow i.ytimg.com poster thumbnails through next/image

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@BC-AdamWard BC-AdamWard force-pushed the feat/product-videos-pdp branch from f35766b to dfe13a9 Compare June 11, 2026 20:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants