Feature/storyblok asset poster - #2652
Merged
Merged
Conversation
A `<video>` paints nothing until it has buffered enough for its first
frame, and nothing at all when autoplay is blocked (iOS Low Power Mode).
An autoplaying video banner therefore starts out black, and can stay
black. `Asset` now takes a `poster`, rendered as `<video poster>`.
Storyblok's `type: asset` has no room for a poster, so `assetWithPoster()`
reads the convention of an asset value carrying an extra `poster` key:
{ "fieldtype": "asset", "filename": "…", "poster": { "filename": "…" } }
Keeping the poster beside the asset rather than nesting both under a
wrapper is what makes a custom field type storing that shape a drop-in
for a plain asset field: existing content stays valid and
`value.filename` keeps working for consumers that ignore the poster. The
narrowing is unavoidable regardless — Storyblok has no JSONSchema for
custom field types, so its type generator emits `unknown` for them.
`@graphcommerce/image` gains `imageUrl()`, which builds an optimized URL
outside of a React tree for the spots that need a bare URL rather than an
`<Image>` — `<video poster>`, `background-image`, `og:image`. It routes
through the configured loader, so the bytes are served and cached by the
deployment instead of every visitor hitting the origin host; that matters
when the origin meters bandwidth. Width snaps up to the nearest
configured size, since the optimizer 400s on anything outside
`imageSizes`/`deviceSizes`.
…Poster Each asset field in the example now goes through `assetWithPoster()` instead of reading `blok.asset` directly, and forwards the resulting `poster` to `<Asset>`. This makes the choice of field type a content decision rather than a code one: a plain `type: asset` and a custom field storing the poster convention both narrow through the same call, so a project can switch any field between the two — in either direction — without touching a component. Fields that never hold a video simply resolve `poster` to undefined.
`HeroBanner` rendered a `<video>` itself, from a raw URL. That meant it
could only ever hold a video, and left nowhere to hang a poster — the one
component where a poster matters most, being a full-bleed autoplaying
video above the fold.
It now takes `asset?: React.ReactNode` and renders it, exactly as its
sibling SpecialBanner already does. Positioning stays with the banner:
whatever is passed gets stretched to fill via `& img, & video`.
- <HeroBanner videoSrc={asset.filename} … />
+ <HeroBanner asset={<Asset asset={asset} poster={poster} />} … />
Taking a node rather than data also keeps next-ui free of any CMS: the
Storyblok and Hygraph examples each pass their own `<Asset>`, both of
which already render images and video. Importing a CMS `Asset` into
next-ui would have inverted the dependency — storyblok-ui and hygraph-ui
depend on next-ui, not the other way round.
Non-breaking: `videoSrc` stays as a deprecated fallback that renders a
bare autoplaying video (keeping the HeroBanner-video class), and the
`animated`/`video` class parts stay exported. The only behavioural change
on the videoSrc path is the loss of the scroll parallax, along with the
framer-motion, useScrollY and clientSize machinery it required.
🦋 Changeset detectedLatest commit: 132d1e1 The changes in this PR will be included in the next version bump. This PR includes changesets to release 86 packages
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 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Let a Storyblok video asset show a poster
Why
An autoplaying
<video>paints nothing until it has buffered enough for itsfirst frame — and nothing at all when autoplay is blocked, as in iOS Low Power
Mode. A full-bleed video banner above the fold therefore starts out black, and
can stay black. There was no way to give it a poster.
What
Three coordinated changes, all minor (no breaking changes):
@graphcommerce/storyblok-uiAssetgains aposter?: StoryblokAssetDataprop, rendered as<video poster>.Ignored for non-video assets.
assetWithPoster(value)helper narrows an asset field's value into{ asset, poster }. It reads the convention of an asset value that carries anextra
posterkey:{ "fieldtype": "asset", "filename": "…", "poster": { "filename": "…" } }is deliberate: a custom field type storing that shape becomes a drop-in for a
plain
type: assetfield — existing content stays valid,value.filenamekeeps working for consumers that ignore the poster, and switching a field over
needs no content migration. The narrowing is unavoidable regardless: Storyblok
has no JSONSchema for custom field types, so its type generator emits
unknownfor them.
@graphcommerce/imageimageUrl(src, { width, quality })builds an optimized image URL outsideof a React tree — for the spots that need a bare URL string rather than an
<Image>:<video poster>, CSSbackground-image,og:image. It routesthrough the configured loader exactly like
<Image>, so the bytes are servedand cached by the deployment's own optimizer instead of every visitor hitting
the origin host.
widthis snapped up to the nearest configured size, sincethe optimizer rejects any width outside
imageSizes/deviceSizes.@graphcommerce/next-uiHeroBannernow takesasset?: React.ReactNodeand renders it, the way itssibling
SpecialBanneralready does, instead of rendering its own<video>from a raw
videoSrc. This lets a banner hold an image or a video, and letsthe caller attach a poster.
videoSrcis deprecated but still works (renders a bare autoplaying video,keeping the
HeroBanner-videoclass), so the change is non-breaking. The onlybehavioural change on that path is the loss of the scroll parallax, along with
the
framer-motion/useScrollY/clientSizemachinery it required.next-uifree of any CMS — importing aCMS
Assetintonext-uiwould invert the dependency direction (storyblok-uiand
hygraph-uidepend onnext-ui, not the reverse).examples/magento-storyblokassetWithPoster()and forwards theposter to
<Asset>. This makes the choice of field type a content decision:a plain
type: assetand a custom field storing the poster convention narrowthrough the same call, so a project can switch any field between the two —
either direction — without touching a component.
examples/magento-graphcms(one file)RowHeroBannermoves off the deprecatedvideoSrconto the newassetprop,passing Hygraph's own
<Asset>. This is the secondHeroBannerconsumer, soit comes along with that change — Hygraph has no poster convention and doesn't
use
assetWithPoster().Verification
Exercised end-to-end on a storefront running this branch: a hero-banner video's
<video>rendersposter="/_next/image?url=…&w=1200&q=75", served by the app'sown optimizer as WebP (~16 KB from a 3840×2160 source, vs ~331 KB fetched
straight from the Storyblok CDN). Existing image and video content renders
unchanged.
tscshows no new errors in any touched package.