The Problem

In a Nuxt 3 project, I was using Universal Viewer (UV) embedded via iframe to display IIIF manifest images. The iframe source pointed to the external https://universalviewer.io/uv.html, but at some point the viewer stopped rendering entirely.

The browser console showed these errors:

SES Removing unpermitted intrinsics
UV.js:2 Unknown content type

Investigation

universalviewer.io Redirect

The first finding was that universalviewer.io now redirects to universalviewer.dev:

<meta http-equiv="refresh" content="0; url=https://universalviewer.dev/uv.html">

Testing the same manifest URL on universalviewer.dev/uv.html produced the identical “Unknown content type” error. The issue was reproducible on the official site itself.

Two Initialization Modes in UV 4.x

UV 4.x ships with two HTML files:

  • uv.html: Designed for iframe embedding. Initializes with IIIFURLAdapter(true) (embedded mode).
  • index.html: Demo page. Initializes with IIIFURLAdapter() (normal mode), explicitly passing iiifManifestId.

The embedded mode in uv.html fails to determine the content type when loading IIIF Presentation API 2.0 manifests, producing the “Unknown content type” error.

The demo page initialization, however, works correctly. This was confirmed on the Netlify-deployed instance:

https://uv-v4.netlify.app/#?manifest=https://kokusho.nijl.ac.jp/biblio/200017711/manifest&cv=80

URL Parameter Format

Another difference was how URL parameters are passed:

  • uv.html (embed): ?manifest=...#?cv=... (query parameter + hash)
  • index.html (demo): #?manifest=...&cv=... (hash parameters only)

The working format uses hash parameters exclusively.

Solution

1. Host UV 4.2.1 Locally

I extracted the necessary files from the npm package into public/uv/:

npm pack universalviewer@4.2.1
tar xzf universalviewer-4.2.1.tgz

Only four items are needed:

public/uv/
├── umd/                 # UV core + chunk JS files (~190 files)
├── uv.css               # Stylesheet
├── uv.html              # Embed page (customized)
└── uv-iiif-config.json  # IIIF configuration

The cjs/, esm/ directories, demo index.html, and collection JSON files can be removed.

2. Custom Embed Page

I rewrote uv.html to use the same initialization pattern as the working demo page:

<script>
  document.addEventListener("DOMContentLoaded", function () {
    var urlAdapter = new UV.IIIFURLAdapter();

    var iiifManifestId =
      urlAdapter.get("iiif-content") ||
      urlAdapter.get("manifest") ||
      urlAdapter.get("iiifManifestId");

    var canvasIndex = urlAdapter.get("cv") || 0;

    var data = urlAdapter.getInitialData({
      iiifManifestId: iiifManifestId,
      canvasIndex: Number(canvasIndex),
    });

    var uv = UV.init("uv", data);
    urlAdapter.bindTo(uv);

    uv.on("configure", function ({ config, cb }) {
      cb(
        fetch("uv-iiif-config.json").then(function (response) {
          return response.json();
        })
      );
    });

    var $UV = document.getElementById("uv");

    function resize() {
      var w = window.innerWidth;
      var h = window.innerHeight;
      $UV.style.width = w + "px";
      $UV.style.height = h + "px";
      if (uv && uv.resize) {
        uv.resize();
      }
    }

    addEventListener("resize", resize);
    resize();
  });
</script>

Key changes:

  • IIIFURLAdapter(true)IIIFURLAdapter() (disable embedded mode)
  • getInitialData({embedded: true})getInitialData({iiifManifestId, canvasIndex}) (pass manifest explicitly)
  • Added a configure handler to load uv-iiif-config.json
  • Call uv.resize() on window resize

3. Update iframe URL Format

On the Vue component side, I switched to hash-parameter URLs:

// Before
`https://universalviewer.io/uv.html?manifest=${field.manifest}#?cv=${cv}`

// After
`/uv/uv.html#?manifest=${field.manifest}&cv=${cv}`

Notes

  • “SES Removing unpermitted intrinsics” is a warning from UV’s internal security mechanism (SES/Hardened JavaScript) and does not affect functionality.
  • The umd/ directory contains approximately 190 chunk JS files. UV.js alone is not sufficient — these chunks are loaded on demand.
  • The manifests tested in this case use IIIF Presentation API 2.0 format (sc:Manifest). Behavior with API 3.0 manifests has not been verified.