An MP4 played audio but showed a black screen. The video track was mp4v.

An MP4 played audio perfectly and still gave me a black screen. The container looked fine, so checking video/mp4 told me basically nothing.

The video track was mp4v.20; audio was AAC. <video> played the audio and gave me nothing for the picture.

The fallback keeps the native audio element as the clock. Video goes to a Worker and then an OffscreenCanvas. The decoder stays lazy. An H.264 control made zero loader or WASM requests.

Parsing the sample table is the scary bit. A bad count can trigger a stupid allocation before decoding even starts, so the parser rejects dimensions above 8192, more than 35 million pixels, or samples over 64 MiB.

The browser test got to frame 7 and stayed within 150 ms of the audio. Fine for preview. Memory still sucks because compressed bytes and decoded frames can overlap.

Would you keep this fallback only for small clips, or reject MP4V and tell users to convert the file?

reddit.com
u/Wooden-Bicycle-6069 — 1 day ago

[AskJS] My Worker build vendored the JS loader but forgot the matching WASM binary

This was awful because a dirty workspace could make the package look complete.

I found a Worker runtime split into libwpd.mjs and libwpd.wasm. The build staged the JS loader. The binary came from another step and was not checked or copied by the Worker package. A clean package could not prove that it owned the runtime it shipped.

I fixed three boring things:

  1. The Emscripten build refreshes both files.
  2. The Worker build copies both files into dist.
  3. The build hashes both files and fails if either one is missing or changed.
const runtime = [
  ['libwpd.mjs', expectedLoaderHash],
  ['libwpd.wasm', expectedWasmHash],
];

for (const [name, expected] of runtime) {
  const bytes = await readFile(resolve('vendor', name));
  const actual = createHash('sha256').update(bytes).digest('hex');
  if (actual !== expected) throw new Error(`${name} checksum mismatch`);
}

The useful rule was simple: the loader and binary are one release unit. "Offline" is not enough if a clean checkout can borrow yesterday's artifact.

Would you commit generated WASM for reproducible installs, or rebuild it in CI and verify the hash there?

reddit.com
u/Wooden-Bicycle-6069 — 2 days ago

[AskJS] I reproduced a PDF.js Worker mismatch caused by dependency hoisting

This one is awful because the build succeeds.

The host app installs pdfjs-dist@6.1.200. A PDF renderer depends on pdfjs-dist@5.4.624. If asset-copy code resolves the Worker from the app root, it can copy 6.1.200. The renderer code still uses API 5.4.624. Vite serves the wrong Worker, and the browser reports a version mismatch.

The fragile version looks like this:

const worker = require.resolve(
  'pdfjs-dist/legacy/build/pdf.worker.mjs'
)

I changed the lookup to resolve pdfjs-dist from the renderer package that owns it. The build now fails if the resolved asset version differs from the renderer dependency.

I ran the harness today across npm and pnpm, nested and hoisted layouts, Vite dev and build, and real cold installs. In every case the copied Worker, CMaps, WASM, and fonts had to come from 5.4.624, while the app kept 6.1.200. The asset manifest also records the source package and version. No more "it probably resolved correctly."

Should build tools always resolve runtime assets from the dependency that owns them, or should packages force one PDF.js version across the whole app?

reddit.com
u/Wooden-Bicycle-6069 — 4 days ago
▲ 1 r/nextjs

My Next.js locale switch looked correct but quietly reset deep links

This was annoying. I moved a docs site to the Next.js App Router and the language switch looked fine on both homepages. On /guide/formats, though, it sent users to /zh instead of /zh/guide/formats. Every deep link lost its place.

I stopped checking only the link label and added this route matrix:

- /guide/formats -> /zh/guide/formats

- /zh/guide/quickstart -> /guide/quickstart

- client navigation -> canonical and og:url both change

- unknown route -> real 404 with no canonical

- mobile and desktop -> the same locale target

The rule is boring now: change only the locale prefix. Keep the remaining path. An explicit locale path must beat stored browser preference.

The second bug was nastier. The page content changed during client navigation while the canonical still described the previous page. The browser test now checks the URL, <html lang>, canonical, and Open Graph URL after every switch.

When the target translation does not exist, would you preserve the exact route and show a missing page, or fall back to the nearest translated section?

reddit.com
u/Wooden-Bicycle-6069 — 4 days ago

Static export did not save me from stale assets after deploy

Static export felt simple until the CDN served new HTML with the wrong generation of JavaScript.

That failure is awful because the build is green. The files exist. A clean browser works. Only users with an older cached asset set get a broken page.

I fixed this by giving every export a deployment ID. The build reads it from the deploy commit, then falls back to the local git SHA. Exported asset URLs carry that ID:

?dpl=<deployment-id>

I also added a build check that fails when the exported homepage has no deployment key.

The test I care about now is:

  1. Open release A.

  2. Deploy release B.

  3. Keep the A tab alive.

  4. Open B in a clean tab.

  5. Verify both tabs load only assets from their own release.

  6. Remove A only after old clients are gone.

Purging everything on deploy looks easy, but it turns old open tabs into random failures. Keeping every release forever is wasteful.

How many previous asset generations do you keep, and what tells you it is safe to delete one?

reddit.com
u/Wooden-Bicycle-6069 — 5 days ago

File Viewer: I split browser preview into 24 lazy pipelines because one giant bundle was awful

I started this in 2022 after being asked to preview Office files with almost no server capacity.

The ugly part was not one renderer. It was getting Worker, WASM, fonts, and resource paths to behave in the same app.

Small correction to the title: the current release has moved on to 208 registered file extensions across 25 lazy preview pipelines.

That includes Office and documents such as PDF, DOC/DOCX, XLS/XLSX, PPT/PPTX, OFD, and Typst; engineering and design files such as DWG, DXF, DWF/DWFx, STEP and other 3D formats, PSD, draw.io, Mermaid, and XMind; plus archives, EML/MSG, EPUB, images, media, source code, SQLite/Parquet, GIS, and EDA files.

The part I care about most: files are parsed and rendered in the browser. No conversion server is required. Search, zoom, print, export, and download use one API. Heavy Worker, WASM, font, and vendor assets load only when needed and can be fully self-hosted for private or offline deployments.

Source: https://github.com/flyfish-dev/file-viewer

Demo: https://demo.file-viewer.app/?locale=en-US

It is not magic. Complex Office layouts can be wrong, and huge files can still destroy a tab. I maintain it.

u/Wooden-Bicycle-6069 — 8 days ago

I started building a browser-side file viewer in 2022 because we had almost no server capacity

This started in 2022, and honestly, it did not start with excitement. It started with pressure.

We did not have enough server capacity, but the requirement did not get smaller. My boss pushed me to build an Office viewer that ran entirely in the browser. I had a deadline and no clear way to render Office files reliably on the client.

I was frustrated and not even sure the request was reasonable.

My first plan was embarrassingly simple: find a library, wrap it, ship it.

Instead, I found a graveyard of partial solutions.

Each library handled only a few formats. Each had its own API, Worker setup, WASM rules, fonts, and resource paths. A demo worked from a CDN and then fell apart offline. I would fix one format and break another. At one point, I had more demos than product.

It was awful.

So I stopped looking for one magical library. I split the problem into format-specific preview pipelines and built a common layer around them: one API, routing and lifecycle, lazy loading, and same-origin Worker/WASM handling. Opening one file does not download every rendering engine.

I did not just merge some libraries and rename the result. I tested what existed, adapted the useful parts, and replaced pieces that would not survive a real browser deployment. The code in the public repo is an independent rewrite and contains no code from the company where this started.

Today File Viewer handles 206 file extensions through 24 preview pipelines. Files stay in the browser. The demo needs no registration.

The ugly parts are still ugly.

Office fidelity is not perfect. Complex layouts, uncommon fonts, charts, embedded objects, and pagination can look wrong. This is a preview, not Microsoft Office rebuilt in JavaScript.

Large files can hurt too. Since parsing and rendering happen in the browser, memory can climb fast. A bad file can slow the page, freeze the tab, or crash it.

I still do not have a clean answer for where browser-side preview should stop and server-side conversion should begin.

What failed first in your product: fidelity, memory, browser compatibility, or deployment?

Demo: https://demo.file-viewer.app/?locale=en-US

Source: https://github.com/flyfish-dev/file-viewer

u/Wooden-Bicycle-6069 — 1 month ago