Closed Kasui92 closed 3 weeks ago
downgrading to 8.0.2 solved the issue
Also seeing this issue on Next 14 during the build process.
8.0.2 has a high security vulnerability due to referencing pdfjs-dist@3.11.174, so it's not a great workaround.
Also experiencing the same issue.
I was able to resolve the issue by setting the dynamic component to load without server-side rendering (SSR).
const PdfViewerComponent = dynamic(() => import("./PdfViewer"), { ssr: false, });
and make sure in next.config.js enable this
config.resolve.alias.canvas = false;
I was able to resolve the issue by setting the dynamic component to load without server-side rendering (SSR).
const PdfViewerComponent = dynamic(() => import("./PdfViewer"), { ssr: false, });
and make sure in next.config.js enable this
config.resolve.alias.canvas = false;
Which version of Next/React-PDF are you using?
Would you be able to paste your full component please?
All, please kindly see the updated samples:
and see updated upgrade guide for workarounds:
@wojtekmaj - I think the upgrade guide should read: experimental.esmExternals: 'loose'
, not experiments
.
Regarding the workarounds... I'm unsure how to use the polyfill for Promise.withResolvers. Is there a guide somewhere on how to do this for react-pdf?
I'm also experiencing this on Remix v2.9.1
. Trying to polyfill Promise.withResolvers now
Edit: It seems that the error is coming from within the worker itself, making this difficult to polyfill. I have tried to place a polyfill both in the root as well as in the client component where I have included the worker, but neither worked for me.
Perhaps using a legacy worker would help? 🤔
Perhaps using a legacy worker would help? 🤔
I tried using a legacy worker, but sadly that did not help. That being said, the following polyfill worked like a charm:
// @ts-expect-error This does not exist outside of polyfill which this is doing
if (typeof Promise.withResolvers === 'undefined') {
if (window)
// @ts-expect-error This does not exist outside of polyfill which this is doing
window.Promise.withResolvers = function () {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
};
}
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/legacy/build/pdf.worker.min.mjs',
import.meta.url
).toString();
Once I added this, it worked fantastically! 😁
I referred to this commit and used a polyfill from core-js. Commit that added the polyfill
As a result, I was able to make it work.
If core-js is available, this method might also work.
Perhaps using a legacy worker would help? 🤔
I tried using a legacy worker, but sadly that did not help. That being said, the following polyfill worked like a charm:
// @ts-expect-error This does not exist outside of polyfill which this is doing if (typeof Promise.withResolvers === 'undefined') { if (window) // @ts-expect-error This does not exist outside of polyfill which this is doing window.Promise.withResolvers = function () { let resolve, reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); return { promise, resolve, reject }; }; } pdfjs.GlobalWorkerOptions.workerSrc = new URL( 'pdfjs-dist/legacy/build/pdf.worker.min.mjs', import.meta.url ).toString();
Once I added this, it worked fantastically! 😁
@justinfarrelldev Where did you add this specifically? I assumed adding to entry.client would be the place, but that didn't resolve it for me.
Perhaps using a legacy worker would help? 🤔
I tried using a legacy worker, but sadly that did not help. That being said, the following polyfill worked like a charm:
// @ts-expect-error This does not exist outside of polyfill which this is doing if (typeof Promise.withResolvers === 'undefined') { if (window) // @ts-expect-error This does not exist outside of polyfill which this is doing window.Promise.withResolvers = function () { let resolve, reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); return { promise, resolve, reject }; }; } pdfjs.GlobalWorkerOptions.workerSrc = new URL( 'pdfjs-dist/legacy/build/pdf.worker.min.mjs', import.meta.url ).toString();
Once I added this, it worked fantastically! 😁
@justinfarrelldev Where did you add this specifically? I assumed adding to entry.client would be the place, but that didn't resolve it for me.
Sorry, I could have been more clear - I put this snippet in the component where the Document and Page tags are (IE, the component used to show the PDF - I called it pdfViewer.client.tsx).
For more context, I also imported the TextLayer.css and AnnotationLayer.css within this file (at the top, as imports) as well as the worker src.
Basically, almost all react-pdf logic is isolated within this client component. I'll post the source code shortly (and edit this comment when I do).
Edit:
Here's the source that works for me (this file is pdfViewer.client.tsx
). Yes, my JSDoc is badly out of date (it was generated with Hygen boilerplate and I haven't updated it). Note that the memoization is to prevent re-renders of the PDF Viewer (causing it to load the PDF again):
/*
Description: The viewer for pdfs. Needs a buffer to render
*/
import React, { FC, useMemo } from 'react';
import { pdfjs, Document, Page } from 'react-pdf';
import 'react-pdf/dist/Page/TextLayer.css';
import 'react-pdf/dist/Page/AnnotationLayer.css';
// @ts-expect-error This does not exist outside of polyfill which this is doing
if (typeof Promise.withResolvers === 'undefined') {
if (window)
// @ts-expect-error This does not exist outside of polyfill which this is doing
window.Promise.withResolvers = function () {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
};
}
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/legacy/build/pdf.worker.min.mjs',
import.meta.url
).toString();
type Props = {
buffer: { data: Buffer };
};
/**
* The viewer for pdfs. Needs a buffer to render
*
* @param {Props} param0
* @param {string} param0.children The children of this component.
* @returns {React.ReactElement}
*/
export const PdfViewer: FC<Props> = ({ buffer }: Props): React.ReactElement => {
const memoizedFile = useMemo(() => buffer.data, [buffer.data]); // Depend on buffer.data assuming buffer.data is stable and only changes if actual data changes
// Memoize the file object to prevent unnecessary re-renders
const fileProp = useMemo(() => ({ data: memoizedFile }), [memoizedFile]);
return (
<div>
<Document
file={fileProp}
onLoadError={(err) =>
console.error(`Loading error from PDF viewer: ${err}`)
}
onLoadStart={() => console.log('Started loading pdf')}
onLoadSuccess={(pdf) =>
console.log('Successfully loaded pdf:', pdf)
}
>
<Page pageIndex={0} />
</Document>
</div>
);
};
The issue is still present.
After running the example app https://github.com/wojtekmaj/react-pdf/tree/main/sample/next-app
I'm getting error in the console
TypeError: Promise.withResolvers is not a function
Using the proposed polyfills doesn't seem to solve it.
The issue is still present.
After running the example app https://github.com/wojtekmaj/react-pdf/tree/main/sample/next-app
I'm getting error in the console
TypeError: Promise.withResolvers is not a function
Using the proposed polyfills doesn't seem to solve it.
Try adding an "else" to the polyfill I posted above and then using "global" instead of window within that else statement. That should help to handle SSR
if (typeof Promise.withResolvers === "undefined") {
if (window) {
// @ts-expect-error This does not exist outside of polyfill which this is doing
window.Promise.withResolvers = function () {
let resolve, reject
const promise = new Promise((res, rej) => {
resolve = res
reject = rej
})
return { promise, resolve, reject }
}
} else {
// @ts-expect-error This does not exist outside of polyfill which this is doing
global.Promise.withResolvers = function () {
let resolve, reject
const promise = new Promise((res, rej) => {
resolve = res
reject = rej
})
return { promise, resolve, reject }
}
}
}
Nothing changes, the same error occurs.
I also tried https://github.com/wojtekmaj/react-pdf/commit/2ba89d8cb968af6e522e688329cbf2e412b80462 with the same result
I referred to this commit and used a polyfill from core-js. Commit that added the polyfill
As a result, I was able to make it work.
If core-js is available, this method might also work.
- Node.js 19.7.0
- "core-js": "^3.37.1"
- "react-pdf": "^9.0.0"
Thank you, It works for me with the same configuration.
Any update on this?, none of above worked for me
Loading a Polyfill and putting import './polyfills.mjs';
at the top of my next.config.mjs
worked for me. This made Promise.withResolvers
available in both browser and server environments.
Here's the code I used:
import 'core-js/full/promise/with-resolvers.js';
// Polyfill for environments where window is not available (e.g., server-side rendering)
if (typeof Promise.withResolvers === 'undefined') {
if (typeof window !== 'undefined') {
window.Promise.withResolvers = function () {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
};
} else {
global.Promise.withResolvers = function () {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
};
}
}
Updated to latest one 9.1.0 and now getting this error, downgraded and still same :| Basically every time when I update version I getting some errors...
Super hacky, but this is how we added the polyfill to the pdfjs worker itself, in vite.config.ts
:
function transformPdfJsWorker(): Plugin {
return {
name: 'transform-pdf-js-worker',
generateBundle(options, bundle) {
for (const [fileName, chunkOrAsset] of Object.entries(bundle)) {
if (!fileName.includes('pdf.worker') || chunkOrAsset.type !== 'asset') {
continue
}
const prepend = Buffer.from(
`if (typeof Promise.withResolvers === "undefined") {
Promise.withResolvers = function () {
let resolve, reject
const promise = new Promise((res, rej) => {
resolve = res
reject = rej
})
return { promise, resolve, reject }
}
}
`,
'utf-8'
)
const sourceBuffer = Buffer.isBuffer(chunkOrAsset.source)
? chunkOrAsset.source
: Buffer.from(chunkOrAsset.source)
chunkOrAsset.source = Buffer.concat([prepend, sourceBuffer])
}
},
}
}
export default defineConfig({
plugins: [
transformPdfJsWorker(),
],
})
if (typeof Promise.withResolvers === "undefined") { if (window) { // @ts-expect-error This does not exist outside of polyfill which this is doing window.Promise.withResolvers = function () { let resolve, reject const promise = new Promise((res, rej) => { resolve = res reject = rej }) return { promise, resolve, reject } } } else { // @ts-expect-error This does not exist outside of polyfill which this is doing global.Promise.withResolvers = function () { let resolve, reject const promise = new Promise((res, rej) => { resolve = res reject = rej }) return { promise, resolve, reject } } } }
Nothing changes, the same error occurs.
I also tried 2ba89d8 with the same result
This works for me if I put the polyfill in _app.tsx. It works in both local and production.
For anyone still struggling with this. Don't bother loading polyfills yourself, it will not work.
You have to load the Legacy version of PdjJS worker which is already polyfilled. See https://github.com/mozilla/pdf.js/wiki/Frequently-Asked-Questions#faq-support
Like so:
import { pdfjs } from "react-pdf";
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/legacy/build/pdf.worker.min.mjs',
import.meta.url,
).toString();
Okay, so after a few tests, you still have to polyfill Promise.withResolvers because react-pdf uses non-legacy pdf.mjs even if you choose the legacy pdf worker. So legacy pdf worker + polyfilled Promise.withResolvers + react-pdf works as intended.
Before you start - checklist
Description
I'm trying to implement the library in a Next.js 14.2.3 (App Router) project without turbopack, however when I go to use the proposed examples it reports the error in the title directly in the console during development or when it's launched the build command.
Having to use Node 20 because Vercel doesn't support 22, I immediately adopted legacy mode to solve the problem... but with poor results.
I also installed
core-js
and imported directly into the layout in root, but although it solves the error during development, it gives me an error when I build the app.Can I ask if I'm missing any steps?
Steps to reproduce
Expected behavior
The build command should work.
Actual behavior
The build command reports an error "failed to parse input file" or "Promise.withResolvers" if core-js is not installed.
Additional information
/src/app/layout.js
/src/app/page.js
/src/app/PdfViewer.js
The example PDF is the same one used in the sample, placed in /public.
Environment