fix(media): add crossOrigin="anonymous" to Image component to fix Firefox OpaqueResponseBlocking - #1108
Conversation
…efox OpaqueResponseBlocking In Firefox, all thumbnail images in the media browser fail to load with OpaqueResponseBlocking errors (NS_BINDING_ABORTED). Sanity's CDN returns Vary: Origin on asset responses; Firefox's stricter ORB blocks opaque cross-origin responses in that case. Adding crossOrigin='anonymous' makes the browser send an Origin header, and the CDN responds with Access-Control-Allow-Origin so Firefox accepts the response. Ports sanity-io/sanity-plugin-media#291
🦋 Changeset detectedLatest commit: 4b414a0 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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.
1 Skipped Deployment
|
The cloud VM's default node (v22.14.0) is below tsdown's ^22.18.0 floor, so 'pnpm build' and 'pnpm test run' fail at @repo/generators with a 'Failed to import module "unrun"' error. Document the cause and how to put a new-enough nvm Node on PATH.
There was a problem hiding this comment.
Pull request overview
This PR addresses a Firefox-specific thumbnail loading failure in sanity-plugin-media by ensuring image requests are made as CORS requests (avoiding Firefox ORB blocking on Sanity CDN responses that use Vary: Origin).
Changes:
- Add
crossOrigin="anonymous"to the shared styled<img>thumbnail component used across the media UI. - Add an agent-facing note explaining a Node.js version pitfall affecting full monorepo builds/tests in the cloud VM environment.
- Add a patch changeset crediting the original author.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| plugins/sanity-plugin-media/src/components/Image/index.tsx | Sets crossOrigin: 'anonymous' on the styled image component to fix Firefox ORB thumbnail loading failures. |
| AGENTS.md | Documents the Node/tsdown version constraint and a workaround for cloud VMs with older node on PATH. |
| .changeset/media-firefox-crossorigin.md | Adds a patch changeset for sanity-plugin-media with author credit and release note text. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…nvention Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
Resolve AGENTS.md Node version notes conflict by keeping main's more complete guidance (covers build, format, and lint needing Node >= 22.18). Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
✅ E2E Tests🟢 24 passed • view full report • view run Studio: https://plugins-e2e-test-studio-itlvfoixi.sanity.dev Datasets: |
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||
…ssorigin-firefox-967b
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## @sanity/sanity-plugin-async-list@3.0.0 ### Major Changes - [#1087](#1087) [`f4e7fcc`](f4e7fcc) Thanks [@stipsan](https://github.com/stipsan)! - Refactor the input into a real React component and fix the `async-list-undefined` namespace/id - **Breaking:** `AsyncList` is now a regular React component that takes a single `props` argument (the standard Sanity input props plus an `options` field), instead of being called as `AsyncList(props, options)`. For the `components.input` slot, use the new `createAsyncListInput(options)` factory: `input: createAsyncListInput({loader})`. This makes the input safe under the Rules of Hooks and lets the React Compiler optimize it. - **Breaking:** the secrets namespace and DOM `id` are no longer derived as `async-list-${schemaType}` (which became the literal `async-list-undefined` for component usage). The DOM `id` now uses Sanity's stable per-field id, and the secrets namespace falls back to `async-list` (instead of `async-list-undefined`) when no `schemaType`/`secrets.namespace` is available. When using the component with `secrets`, set an explicit `secrets.namespace`; a dev warning is logged if it is missing. - Fix: the debounced search handler is stable and is cancelled on unmount, so it no longer drops queued calls or updates state on an unmounted tree. - Fix: the value-change handler no longer depends on the whole `props` object, avoiding unnecessary `Autocomplete` re-renders. - Fix: loader results are validated to ensure each option's `value` is a string. ### Patch Changes - Updated dependencies [[`c61bb44`](c61bb44)]: - @sanity/studio-secrets@4.0.15 ## @sanity/personalization-plugin@3.1.0 ### Minor Changes - [#1226](#1226) [`1143bd6`](1143bd6) Thanks [@jjburbridge](https://github.com/jjburbridge)! - Add `fieldLevelPersonalization` export for segment-based field personalization. Segments can be defined statically, fetched from an external API, or stored in your Sanity dataset. `FieldPluginConfig` is renamed to `ExperimentFieldPluginConfig` (a deprecated type alias is kept for compatibility). ### Patch Changes - [#1208](#1208) [`51317b2`](51317b2) Thanks [@stipsan](https://github.com/stipsan)! - Address review feedback from the migration into the monorepo: - Fix LaunchDarkly experiment pagination so `offset` advances between pages, preventing duplicate results and a possible infinite loop - Correct misspelled plugin names (`personalistaion` → `personalization`) and stop the LaunchDarkly entry point from identifying itself as the GrowthBook plugin - Fix an operator-precedence bug in the experiment field preview that could set the title to the entire field object - Walk reference preview paths with optional chaining to avoid throwing on missing intermediate fields - Move the experiment item activation patch out of render and into an effect - Remove unused `Select` props - Fix the GrowthBook (`project` config key) and LaunchDarkly (`fieldLevelExperiments` usage) docs and drop the stale standalone-repo Studio version / tooling references - Updated dependencies [[`c61bb44`](c61bb44)]: - @sanity/studio-secrets@4.0.15 ## sanity-plugin-cloudinary@2.1.0 ### Minor Changes - [#1233](#1233) [`36f3fe2`](36f3fe2) Thanks [@ChrisLaRocque](https://github.com/ChrisLaRocque)! - Add performant preview images Asset previews and diffs now build an optimized, scaled-down preview URL with `@cloudinary/url-gen` (a 400px-wide transformation) when a cloud name is configured, instead of loading the full-size original. This keeps the Studio fast when previewing large Cloudinary assets, and falls back to the stored asset URL when no cloud name is available. - [#1227](#1227) [`3b02635`](3b02635) Thanks [@pgurley](https://github.com/pgurley), [@atlvis](https://github.com/atlvis)! - Add `cloudinaryReferencePlugin` for storing Cloudinary assets as reusable document references - New `cloudinaryReferencePlugin` registers the schema types needed to reference Cloudinary assets as documents - New `cloudinaryAssetDocument` type stores a Cloudinary asset as a standalone document - New `cloudinaryAssetReference` type references those asset documents, with a custom input for selecting and managing assets through the Cloudinary Media Library - `openMediaSelector` now supports a `showHandler` callback and a `folder` option, so the select button can show a loading state and scope the library to a folder - Fixed the internal name of `cloudinaryAssetSourcePlugin` (`cloudinart-asset-source` → `cloudinary-asset-source`) ### Patch Changes - [#1236](#1236) [`7ec89b6`](7ec89b6) Thanks [@stipsan](https://github.com/stipsan)! - - Fix the `cloudinaryAssetSourcePlugin` name, which was mistakenly registered as `cloudinart-asset-source` - Wait for the Cloudinary Media Library script to finish loading before opening it, avoiding runtime errors when several inputs mount at once - Fix a user-facing typo in the asset source loading message ("Media Libary" → "Media Library") - Remove an invalid `src`-less `<track>` element from the video preview - Correct the README usage examples (`defineConfg` → `defineConfig`) and drop stale standalone-repo "Develop & test" / "Release new version" instructions - Updated dependencies [[`c61bb44`](c61bb44)]: - @sanity/studio-secrets@4.0.15 ## sanity-plugin-media@6.1.0 ### Minor Changes - [#1119](#1119) [`ab39674`](ab39674) Thanks [@lud-hu](https://github.com/lud-hu)! - Add an "Edit Media" asset source so media details (alt text, title, tags, etc.) can be edited directly from an image or file field on a document, without opening the full Media tool. - [#1117](#1117) [`6f4316d`](6f4316d) Thanks [@nickeforsberg](https://github.com/nickeforsberg)! - Add an `excludeTags` option that hides assets referencing the listed `media.tag` slugs (`name.current` values). Excluded assets are omitted from the Media browser grid and asset-picker queries, and the matching tags are hidden from the tag sidebar and tag search facet. The asset edit dialog still lists all tags so you can assign or remove them on an open asset. - [#1121](#1121) [`30fa7e2`](30fa7e2) Thanks [@bobbygeo](https://github.com/bobbygeo), [@pedrobonamin](https://github.com/pedrobonamin)! - Add folder management to the media browser - Organise assets into nested folders, backed by a new `media.folder` document type (a `name` plus a weak `parent` reference) and a single weak `opt.media.folder` reference on each asset (mirroring how tags work). Renaming a folder is a one-field document write regardless of how many assets it contains. - Browse folders in a dedicated sidebar tree, with breadcrumb navigation, create / rename / delete flows, a bulk "Move to folder" dialog, and per-asset folder controls in the asset details dialog. The default "All assets" view lists every asset; opening a folder filters the list to that folder. - Deleting a folder removes only the folder document — its assets stay in the library with their folder assignment cleared, and nested folders move up one level. - Support multi-select insert when the media plugin is used as an asset source for array (multiple) fields. - [#1115](#1115) [`e7ec6e6`](e7ec6e6) Thanks [@am0wa](https://github.com/am0wa)! - Extract `image` (`sanity.imageExifTags`) metadata on asset upload and include it in asset queries. The asset edit dialog now pre-fills the Description field from the image's `ImageDescription` EXIF tag as a fallback for image assets. - [#1118](#1118) [`186e29c`](186e29c) Thanks [@Polleke007](https://github.com/Polleke007), [@joepvandepol](https://github.com/joepvandepol)! - Add the ability to replace a selected asset with another one and update all of its references across documents When a single asset is selected, a new **Replace** action opens an overview where you can pick a replacement asset. Every document that references the original asset (including deeply nested image fields) is re-pointed to the chosen asset. Ported from sanity-io/sanity-plugin-media#236. ### Patch Changes - [#1108](#1108) [`1dc3a7b`](1dc3a7b) Thanks [@oxygensmith](https://github.com/oxygensmith)! - Add `crossOrigin="anonymous"` to the image thumbnail component so thumbnails load in Firefox. Firefox's Opaque Response Blocking would otherwise block Sanity CDN asset responses (which send `Vary: Origin`) when requested without an `Origin` header. ## @sanity/plugin-kit@10.0.3 ### Patch Changes - [#1698](#1698) [`d3dedd3`](d3dedd3) Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! - fix(deps): update dependency oxlint to ^1.76.0 - [#1779](#1779) [`5555795`](5555795) Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! - fix(deps): update dependency oxfmt to ^0.61.0 - [#1799](#1799) [`93858fc`](93858fc) Thanks [@stipsan](https://github.com/stipsan)! - Prefer function components via `react/prefer-function-component` (with `allowErrorBoundary`) instead of banning `Component`/`PureComponent` imports ## @sanity/cross-dataset-duplicator@2.0.13 ### Patch Changes - [#1228](#1228) [`71d77d5`](71d77d5) Thanks [@rohanvachheta](https://github.com/rohanvachheta)! - Handle reference errors during duplication by fetching missing referenced documents (including transitive refs, respecting `filter`), re-uploading any recovered assets, and retrying the transaction, with a one-by-one commit fallback - Updated dependencies [[`c61bb44`](c61bb44)]: - @sanity/studio-secrets@4.0.15 ## @sanity/embeddings-index-ui@4.0.13 ### Patch Changes - [#1792](#1792) [`c61bb44`](c61bb44) Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! - fix(deps): update dependency react-rx to ^4.2.5 ## @sanity/form-toolkit@3.0.13 ### Patch Changes - [#1234](#1234) [`1dd36c8`](1dd36c8) Thanks [@stipsan](https://github.com/stipsan)! - Address post-migration review feedback: - `FormRenderer` now renders interactive (uncontrolled) inputs when no `getFieldState` is provided, so the documented native HTML form usage works out of the box - Uncontrolled text and textarea fields honor `options.defaultValue` (controlled fields keep form-library state only, so UI and submit values stay in sync) - `FormRenderer` falls back to `field.name` for the React key when a field has no `_key` - The shared HubSpot/Mailchimp request handler defaults to the Next.js-compatible handler instead of throwing when no framework environment variable is detected, and short-circuits CORS preflight (`OPTIONS`) requests - Corrected the `formSchema`, `formiumInput`, and `mailchimpInput` usage examples (import paths and required options), README typos, and example/dev-workflow references - Updated dependencies [[`f4e7fcc`](f4e7fcc)]: - @sanity/sanity-plugin-async-list@3.0.0 ## @sanity/language-filter@5.0.14 ### Patch Changes - [#1792](#1792) [`c61bb44`](c61bb44) Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! - fix(deps): update dependency react-rx to ^4.2.5 ## @sanity/studio-secrets@4.0.15 ### Patch Changes - [#1792](#1792) [`c61bb44`](c61bb44) Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! - fix(deps): update dependency react-rx to ^4.2.5 ## sanity-plugin-hotspot-array@5.0.12 ### Patch Changes - [#1789](#1789) [`9628df4`](9628df4) Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! - fix(deps): update dependency motion to ^12.43.0 ## sanity-plugin-iframe-pane@5.0.30 ### Patch Changes - [#1789](#1789) [`9628df4`](9628df4) Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! - fix(deps): update dependency motion to ^12.43.0 ## sanity-plugin-internationalized-array@5.1.25 ### Patch Changes - [#1765](#1765) [`97937c4`](97937c4) Thanks [@pedrobonamin](https://github.com/pedrobonamin)! - Add a stable `data-testid` on the document-level add-translations panel for e2e coverage ## sanity-plugin-mux-input@5.0.8 ### Patch Changes - [#1792](#1792) [`c61bb44`](c61bb44) Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! - fix(deps): update dependency react-rx to ^4.2.5 ## sanity-plugin-studio-smartling@5.0.14 ### Patch Changes - [#1216](#1216) [`d4dd45b`](d4dd45b) Thanks [@stipsan](https://github.com/stipsan)! - Harden the Smartling adapter and fix docs: - Send the secret credentials JSON verbatim during authentication instead of double-encoding it, which could break auth with proxies that forward the request body as-is - Throw a clear error (surfacing Smartling's message) when authentication does not return an access token, instead of a cryptic `TypeError` - Avoid throwing when no existing job is found or when a translation/progress response is missing its expected payload - Guard the progress calculation against a zero total word count so empty documents no longer report `NaN`/`Infinity` - Fix the `additionalDeserializers` option name in the advanced configuration docs (was `additonalDeserializers`) and update the README development/release instructions for the monorepo ## sanity-plugin-workflow@3.0.39 ### Patch Changes - [#1789](#1789) [`9628df4`](9628df4) Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! - fix(deps): update dependency motion to ^12.43.0 ## @sanity/document-internationalization@6.2.28 ## @sanity/sfcc@1.0.25 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > <sup>[Cursor Bugbot](https://cursor.com/bugbot) is generating a summary for commit 9246b02. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: squiggler-app[bot] <265501495+squiggler-app[bot]@users.noreply.github.com>
Ports sanity-io/sanity-plugin-media#291 by @oxygensmith into the monorepo.
Problem
In Firefox, all thumbnail images in the media browser fail to load with
OpaqueResponseBlockingerrors (NS_BINDING_ABORTEDin the network panel). This does not affect Chrome or Safari.Root cause: Sanity's CDN returns
Vary: Originon all asset responses. Firefox's stricter Opaque Response Blocking (ORB) implementation blocks opaque (no-cors) cross-origin responses when the server declaresVary: Origin. TheImagecomponent renders a plain<img>without acrossOriginattribute, so the browser makes a no-cors request and receives an opaque response — which Firefox blocks.Fix
Adding
.attrs({crossOrigin: 'anonymous' as const})to theImagestyled component causes the browser to include anOriginheader with every image request. Sanity's CDN already responds withAccess-Control-Allow-Originwhen anOriginis present, so Firefox accepts the response. This affects every place in the plugin that renders image thumbnails (card view, table view, edit dialog).Port notes
@oxygensmithso the release notes thank them..attrs({crossOrigin: 'anonymous' as const})— while keeping the template literal valid so it builds and passes this monorepo's strict CI.AGENTS.mdnote (separate commit): the cloud VM's default Node (v22.14.0) is belowtsdown's^22.18.0floor, which breaks the fullpnpm build/pnpm test runat@repo/generatorsuntil a newer Node is put onPATH.Testing
Verified in the test studio (Kitchen Sink workspace → Media tool) that every rendered thumbnail
<img>carriescrossorigin="anonymous"and loads correctly. A console snippet confirmed 100% coverage (total: 20, withCrossOriginAnonymous: 20, sampleCrossOrigin: "anonymous").media_thumbnails_crossorigin_anonymous_demo.mp4
Media browser thumbnails loaded
Elements panel showing crossorigin attribute
Console proof: all CDN images have crossOrigin anonymous
Programmatic checks (run with Node
v22.22.2):pnpm format— no changespnpm lint— passespnpm build— 40/40 tasks passpnpm test run— 852/852 tests pass (149 media tests included)To show artifacts inline, enable in settings.