Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Next.js and v9 on build Promise.withResolvers #1811

Closed
4 tasks done
Kasui92 opened this issue Jun 1, 2024 · 53 comments
Closed
4 tasks done

Next.js and v9 on build Promise.withResolvers #1811

Kasui92 opened this issue Jun 1, 2024 · 53 comments
Labels
question Further information is requested

Comments

@Kasui92
Copy link

Kasui92 commented Jun 1, 2024

Before you start - checklist

  • I followed instructions in documentation written for my React-PDF version
  • I have checked if this bug is not already reported
  • I have checked if an issue is not listed in Known issues
  • If I have a problem with PDF rendering, I checked if my PDF renders properly in PDF.js demo

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

  1. Install Next.js App
  2. Install React-Pdf v9
  3. Install core-js
  4. Use the files in information paragragh
  5. Try to build

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

import 'core-js/full/promise/with-resolvers'

export default function RootLayout({ children }) {
    return (
        <html lang="en">
            <body>
                <main >{children}</main>
            </body>
        </html>
    )
}

/src/app/page.js

import PdfViewer from './_components/PdfViewer'

export default async function ReactPDF() {
    return (
        <div>
                    <PdfViewer />
        </div>
    )
}

/src/app/PdfViewer.js

'use client'

import { useCallback, useState } from 'react'
import { pdfjs, Document, Page } from 'react-pdf'
import 'react-pdf/dist/esm/Page/AnnotationLayer.css'
import 'react-pdf/dist/esm/Page/TextLayer.css'

pdfjs.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/legacy/build/pdf.worker.min.mjs', import.meta.url).toString()

const options = {
    cMapUrl: '/cmaps/',
    standardFontDataUrl: '/standard_fonts/',
}

const maxWidth = 800

export default function PdfViewer() {
    const [file, setFile] = useState('./sample.pdf')
    const [numPages, setNumPages] = useState()
    const [containerRef, setContainerRef] = useState(null)
    const [containerWidth, setContainerWidth] = useState()

    const onResize = useCallback((entries) => {
        const [entry] = entries

        if (entry) {
            setContainerWidth(entry.contentRect.width)
        }
    }, [])

    function onFileChange(event) {
        const { files } = event.target

        const nextFile = files?.[0]

        if (nextFile) {
            setFile(nextFile)
        }
    }

    function onDocumentLoadSuccess({ numPages: nextNumPages }) {
        setNumPages(nextNumPages)
    }

    return (
        <div className="Example">
            <div className="Example__container">
                <div className="Example__container__document" ref={setContainerRef}>
                    <Document file={file} onLoadSuccess={onDocumentLoadSuccess} options={options}>
                        {Array.from(new Array(numPages), (el, index) => (
                            <Page
                                key={`page_${index + 1}`}
                                pageNumber={index + 1}
                                width={containerWidth ? Math.min(containerWidth, maxWidth) : maxWidth}
                            />
                        ))}
                    </Document>
                </div>
            </div>
        </div>
    )
}

The example PDF is the same one used in the sample, placed in /public.

Environment

  • React-PDF version: v9.0.0
  • React version: 18.3.1
  • Node version: 20.14.0
@Kasui92 Kasui92 added the bug Something isn't working label Jun 1, 2024
@lucaslosi
Copy link

downgrading to 8.0.2 solved the issue

@craigcartmell
Copy link

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.

@allicanseenow

This comment was marked as spam.

@vpsk
Copy link

vpsk commented Jun 6, 2024

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;

@craigcartmell
Copy link

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?

@wojtekmaj
Copy link
Owner

All,
please kindly see the updated samples:

and see updated upgrade guide for workarounds:

@craigcartmell
Copy link

@wojtekmaj - I think the upgrade guide should read: experimental.esmExternals: 'loose', not experiments.

@NikkiHess
Copy link

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?

@justinfarrelldev
Copy link

justinfarrelldev commented Jun 10, 2024

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.

@wojtekmaj
Copy link
Owner

Perhaps using a legacy worker would help? 🤔

@justinfarrelldev
Copy link

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! 😁

@fate7bardiche
Copy link

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"

@justinwaite
Copy link

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.

@justinfarrelldev
Copy link

justinfarrelldev commented Jun 16, 2024

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>
    );
};

@adrianbienias
Copy link

adrianbienias commented Jun 23, 2024

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.

@justinfarrelldev
Copy link

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

@adrianbienias
Copy link

adrianbienias commented Jun 23, 2024

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

@maxess3
Copy link

maxess3 commented Jun 23, 2024

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.

@siinghd
Copy link

siinghd commented Jun 23, 2024

Any update on this?, none of above worked for me

@Ori2846
Copy link

Ori2846 commented Jun 25, 2024

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 };
    };
  }
}

@DonikaV
Copy link

DonikaV commented Jul 2, 2024

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...

@ldiqual
Copy link

ldiqual commented Jul 3, 2024

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(),
  ],
})

@allicanseenow
Copy link

allicanseenow commented Jul 4, 2024

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.

@gabsoftware
Copy link

gabsoftware commented Jul 5, 2024

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();

@gabsoftware
Copy link

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.

@aldwnesx
Copy link

aldwnesx commented Jul 7, 2024

Getting the same error:

 ○ Compiling / ...

warn - No utility classes were detected in your source files. If this is unexpected, double-check the `content` option in your Tailwind CSS configuration.
warn - https://tailwindcss.com/docs/content-configuration
 ✓ Compiled / in 673ms (567 modules)
 ⨯ TypeError: Promise.withResolvers is not a function
    at __webpack_require__ (/Users/al/Documents/GitHub/test/frontend/.next/server/webpack-runtime.js:33:42)
    at eval (./app/page.tsx:8:67)
    at (ssr)/./app/page.tsx (/Users/al/Documents/GitHub/test/frontend/.next/server/app/page.js:173:1)
    at Object.__webpack_require__ [as require] (/Users/al/Documents/GitHub/test/frontend/.next/server/webpack-runtime.js:33:42)
    at JSON.parse (<anonymous>)
digest: "1833689360"

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();

@DonikaV
Copy link

DonikaV commented Jul 19, 2024

@wojtekmaj Hey, sorry, is there any proper solution for this error?
pdfjs.GlobalWorkerOptions.workerSrc = //unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs;

and this
import 'core-js/actual/promise';

Doesnt help. The latest version of Chrome and nextjs 14.2.5 and this error.

@jesus-maryapp
Copy link

jesus-maryapp commented Aug 10, 2024

For anyone struggling with remix (v2.10.2 and nodejs 20 LTS).

This worked for me: I created a promisePolyfill.ts util function then imported it at the very top of the file that imports everything related to react-pdf or pdfjs-dist.

//promisePolyfill.ts
if (typeof Promise.withResolvers !== "function") {
  Promise.withResolvers = function <T>() {
    let resolve!: (value: T | PromiseLike<T>) => void;
    let reject!: (reason?: any) => void;
    const promise = new Promise<T>((res, rej) => {
      resolve = res;
      reject = rej;
    });
    return { promise, resolve, reject };
  };
}

export {};
//yourfile.client.tsx
import "~/utils/promisePolyfill";
import { useCallback, useRef, useState } from "react";
import { useResizeObserver } from "@wojtekmaj/react-hooks";
import { pdfjs, Document, Page } from "react-pdf";

import "react-pdf/dist/esm/Page/AnnotationLayer.css";
import "react-pdf/dist/esm/Page/TextLayer.css";
import { PDFToolbar } from "./pdfToolbar";
import { LoaderCircle } from "lucide-react";
import { IPDFViewerProps } from "~/types/pdfViewerTypes";

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
  "/pdf.worker.min.mjs",
  import.meta.url,
).toString();

Do make sure that that your file is only rendering on the client side, so rename your file to : file.client.tsx

Also if you have issues with the typing for the promise polyfill remember to create your global.d.ts for it:

interface PromiseConstructor {
  withResolvers<T>(): {
    promise: Promise<T>;
    resolve: (value: T | PromiseLike<T>) => void;
    reject: (reason?: any) => void;
  };
}

@ssxdev
Copy link

ssxdev commented Aug 12, 2024

page.tsx:

const PDFViewer = dynamic(
  () => import('./pdf-viewer').then(mod => mod.PDFViewer),
  { ssr: false }
)

pdf-viewer.tsx

"use client"

import { Document, Page, pdfjs } from 'react-pdf'

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
  'pdfjs-dist/build/pdf.worker.min.mjs',
  import.meta.url
).toString()

export function PDFViewer() {
  return (
    <Document>
      <Page />
    </Document>
  )

@oaitbaziz
Copy link

It fixed my tests execution which was failing since I added react-pdf to my project.

I've tried all solutions above nothing worked until I found your solution, thank you!

@Dmytro-Uchkin-Ytree
Copy link

Dmytro-Uchkin-Ytree commented Aug 14, 2024

@wojtekmaj The issue is still reproducible in some cases
Chrome: 127
React: 18.2.0
image

The issue is related to pdf.js dependency.
https://github.com/mozilla/pdf.js/issues?q=withResolvers

@Dmytro-Uchkin-Ytree
Copy link

@wojtekmaj The issue is still reproducible in some cases Chrome: 127 React: 18.2.0 image

The issue is related to pdf.js dependency. https://github.com/mozilla/pdf.js/issues?q=withResolvers

UPDATE:
Tuned out Promise.withResolvers was overwritten by Zone.js (angular) (ZoneAwarePromise) Promise class whose old implementation does Not have withResolvers method.

Solution would be updating conflicting dependancies, especially if you're using Angular

@dsternlicht
Copy link

This works for me

put this in the component where you use the Document component

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
    'pdfjs-dist/legacy/build/pdf.worker.min.mjs',
    import.meta.url,
).toString();

Add this on the page.tsx of the route.


if (typeof Promise.withResolvers === "undefined") {
    if (typeof window !== 'undefined') {
      // @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 }
      }
    }
  }

Worked for me as well. Thanks!

@Bfaschat
Copy link

Bfaschat commented Aug 19, 2024

Just wanted to throw some observations I discovered while testing this, For those still facing this issue while using react + Next JS Pages + Promise.withResolvers bypass

If you are planning to host your project on Vercel or any other hosting that doesn't have node 22.x support yet, below is the working solution.

@Kasui92 this error occurs because Vercel currently defaults to Node.js 20.x, while Promise.withResolvers() is available only in Node.js 22.x or later.

My project config uses the below packages:

  1. React version: 18.3.1
  2. Next.js version: 14.2.5
  3. Node.js version: v20.x
  4. react-pdf version: 9.1.0
  5. core-js version: 3.38.0

Here’s a polyfill solution for Promise.withResolvers() in both TypeScript and JavaScript:

TypeScript:

// utils/polyfilsResolver.tsx

/**
 * Polyfill for Promise.withResolvers if it's not available.
 */
 
export type PromiseWithResolvers<T> = {
  promise: Promise<T>;
  resolve: (value: T | PromiseLike<T>) => void;
  reject: (reason?: any) => void;
};

export function polyfillPromiseWithResolvers() {
  if (!Promise.withResolvers) {
    Promise.withResolvers = function <T>(): PromiseWithResolvers<T> {
      let resolve: (value: T | PromiseLike<T>) => void;
      let reject: (reason?: any) => void;

      const promise = new Promise<T>((res, rej) => {
        resolve = res;
        reject = rej;
      });

      return { promise, resolve: resolve!, reject: reject! };
    };
  }
}

Javascript

// utils/polyfilsResolver.js

/**
 * Polyfill for Promise.withResolvers if it's not available.
 */
export function polyfillPromiseWithResolvers() {
    if (!Promise.withResolvers) {
      Promise.withResolvers = function () {
        let resolve;
        let reject;
  
        const promise = new Promise((res, rej) => {
          resolve = res;
          reject = rej;
        });
  
        return { promise, resolve, reject };
      };
    }
  }

Now You can call polyfillPromiseWithResolvers() from _app.js or _app.jsx like below

// pages/_app.js or pages/_app.jsx

import { polyfillPromiseWithResolvers } from "@/utils/polyfilsResolver";

import 'core-js/full/promise/with-resolvers.js';

polyfillPromiseWithResolvers();

Now lets use the legacy version of react-pdf as the final touch

// Pdfviewer component 

pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;

Finally, lets build it for production to test

npm run build

Note: This is a working solution that i use on my active projects. It works 100% on vercel and other hostings that doesn't support node 20.x

Enjoy XD 💯

@Kasui92
Copy link
Author

Kasui92 commented Aug 19, 2024

@Bfaschat
Oh, sounds like it's good for my case!

Thank you for the comments and the working solution!

@Bfaschat
Copy link

@Bfaschat Oh, sounds like it's good for my case!

Thank you for the comments and the working solution!

Merci <3

@Bfaschat
Copy link

Bfaschat commented Aug 20, 2024

Just wanted to throw some observations I discovered while testing this, For those still facing this issue while using react + Next JS Pages + Promise.withResolvers bypass

If you are planning to host your project on Vercel or any other hosting that doesn't have node 22.x support yet, below is the working solution.

@Kasui92 this error occurs because Vercel currently defaults to Node.js 20.x, while Promise.withResolvers() is available only in Node.js 22.x or later.

My project config uses the below packages:

  1. React version: 18.3.1
  2. Next.js version: 14.2.5
  3. Node.js version: v20.x
  4. react-pdf version: 9.1.0
  5. core-js version: 3.38.0

Here’s a polyfill solution for Promise.withResolvers() in both TypeScript and JavaScript:

TypeScript:

// utils/polyfilsResolver.tsx

/**
 * Polyfill for Promise.withResolvers if it's not available.
 */
 
export type PromiseWithResolvers<T> = {
  promise: Promise<T>;
  resolve: (value: T | PromiseLike<T>) => void;
  reject: (reason?: any) => void;
};

export function polyfillPromiseWithResolvers() {
  if (!Promise.withResolvers) {
    Promise.withResolvers = function <T>(): PromiseWithResolvers<T> {
      let resolve: (value: T | PromiseLike<T>) => void;
      let reject: (reason?: any) => void;

      const promise = new Promise<T>((res, rej) => {
        resolve = res;
        reject = rej;
      });

      return { promise, resolve: resolve!, reject: reject! };
    };
  }
}

Javascript

// utils/polyfilsResolver.js

/**
 * Polyfill for Promise.withResolvers if it's not available.
 */
export function polyfillPromiseWithResolvers() {
    if (!Promise.withResolvers) {
      Promise.withResolvers = function () {
        let resolve;
        let reject;
  
        const promise = new Promise((res, rej) => {
          resolve = res;
          reject = rej;
        });
  
        return { promise, resolve, reject };
      };
    }
  }

Now You can call polyfillPromiseWithResolvers() from _app.js or _app.jsx like below

// pages/_app.js or pages/_app.jsx

import { polyfillPromiseWithResolvers } from "@/utils/polyfilsResolver";

import 'core-js/full/promise/with-resolvers.js';

polyfillPromiseWithResolvers();

Now lets use the legacy version of react-pdf as the final touch

// Pdfviewer component 

pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;

Finally, lets build it for production to test

npm run build

Note: This is a working solution that i use on my active projects. It works 100% on vercel and other hostings that doesn't support node 20.x

Enjoy XD 💯

Hey everyone :) Make sure you are running npm run build or yarn run build using node 20.x not 22.x

Trying to use Node 22.x gives a new error, i didn't have time to debug it so i defaulted back to 20.x

I hope my solution above works as expected

Happy coding

@wojtekmaj wojtekmaj mentioned this issue Aug 22, 2024
4 tasks
@robiulhr
Copy link

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! 😁

This worked for me.

@zeeklog
Copy link

zeeklog commented Sep 2, 2024

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(),
  ],
})

Excellent!!! It worked for me.

@wojtekmaj wojtekmaj added question Further information is requested and removed bug Something isn't working labels Sep 2, 2024
@Alwaz
Copy link

Alwaz commented Sep 7, 2024

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;

Thankyouuu soo muchhh you saved me!!!

@luuknnh
Copy link

luuknnh commented Sep 17, 2024

page.tsx:

const PDFViewer = dynamic(
  () => import('./pdf-viewer').then(mod => mod.PDFViewer),
  { ssr: false }
)

pdf-viewer.tsx

"use client"

import { Document, Page, pdfjs } from 'react-pdf'

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
  'pdfjs-dist/build/pdf.worker.min.mjs',
  import.meta.url
).toString()

export function PDFViewer() {
  return (
    <Document>
      <Page />
    </Document>
  )

This still doesn't work for me when I use Nextjs 14.2.11 :(

When I run npm run build I get a craaazy long error with lots of whitespace and this on the bottom:
`----

Caused by:
0: failed to parse input file
1: Syntax Error

Build failed because of webpack errors

@rlawnsrud0509
Copy link

페이지.tsx:

const PDFViewer = dynamic(
  () => import('./pdf-viewer').then(mod => mod.PDFViewer),
  { ssr: false }
)

pdf-뷰어.tsx

"use client"

import { Document, Page, pdfjs } from 'react-pdf'

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
  'pdfjs-dist/build/pdf.worker.min.mjs',
  import.meta.url
).toString()

export function PDFViewer() {
  return (
    <Document>
      <Page />
    </Document>
  )

Nextjs 14.2.11을 사용해도 여전히 작동하지 않아요 :(

npm run build를 실행하면 공백이 많은 엄청나게 긴 오류가 발생하고 하단에 다음과 같은 내용이 표시됩니다. `----

원인: 0: 입력 파일 구문 분석 실패 1: 구문 오류

webpack 오류로 인해 빌드가 실패했습니다.

add this code to next.config.mjs

webpack: (config) => {
    config.optimization.minimize = false;

    return config;
},

@Bumboobee
Copy link

if (typeof Promise.withResolvers === "undefined") {
if (typeof window !== 'undefined') {
// @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 }
}
}
}

After many tried so hard with the previous answers and the error still remaning, @armanmasangkay solution works for me just well, thanks a lot bro!

@Tekutorukushi
Copy link

page.tsx:

const PDFViewer = dynamic(
  () => import('./pdf-viewer').then(mod => mod.PDFViewer),
  { ssr: false }
)

pdf-viewer.tsx

"use client"

import { Document, Page, pdfjs } from 'react-pdf'

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
  'pdfjs-dist/build/pdf.worker.min.mjs',
  import.meta.url
).toString()

export function PDFViewer() {
  return (
    <Document>
      <Page />
    </Document>
  )

This still doesn't work for me when I use Nextjs 14.2.11 :(

When I run npm run build I get a craaazy long error with lots of whitespace and this on the bottom: `----

Caused by: 0: failed to parse input file 1: Syntax Error

Build failed because of webpack errors

I had the same mistake, but do it
pdfjs.GlobalWorkerOptions.workerSrc = //unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs;

@DanielWilliamClarke
Copy link

page.tsx:

const PDFViewer = dynamic(
  () => import('./pdf-viewer').then(mod => mod.PDFViewer),
  { ssr: false }
)

pdf-viewer.tsx

"use client"

import { Document, Page, pdfjs } from 'react-pdf'

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
  'pdfjs-dist/build/pdf.worker.min.mjs',
  import.meta.url
).toString()

export function PDFViewer() {
  return (
    <Document>
      <Page />
    </Document>
  )

This still doesn't work for me when I use Nextjs 14.2.11 :(
When I run npm run build I get a craaazy long error with lots of whitespace and this on the bottom: `----
Caused by: 0: failed to parse input file 1: Syntax Error

Build failed because of webpack errors

I had the same mistake, but do it pdfjs.GlobalWorkerOptions.workerSrc = //unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs;

This along with this polyfill works for me 🎉

This works for me
put this in the component where you use the Document component

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
    'pdfjs-dist/legacy/build/pdf.worker.min.mjs',
    import.meta.url,
).toString();

Add this on the page.tsx of the route.


if (typeof Promise.withResolvers === "undefined") {
    if (typeof window !== 'undefined') {
      // @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 }
      }
    }
  }

Worked for me as well. Thanks!

@vicapow
Copy link

vicapow commented Oct 13, 2024

TLDR - Next.JS is trying to run client side code, server side but some of that code relies on APIs that don't exist in Node.JS env (Promise.withResolvers)? This is complicated by the fact that the web worker code for pdfjs loaded from a CDN isn't code react-pdf has control over (so I think is really the wrong place for this issue.)

If you want to run the non-legacy pdfjs web worker code from a Next.JS, I found one way to do that with something like this:

// So we can at least still get typescript type info,
// import only the types for react-pdf
import type * as reactPdfType from "react-pdf";
// Workaround for https://github.com/wojtekmaj/react-pdf/issues/1811
let reactPdf: typeof reactPdfType | undefined;
if (typeof window !== "undefined") {
  reactPdf = require("react-pdf");
}

// URL for the worker file (if you're using the CDN)
// We need to use the CDN link to try and work around
// this issue: https://github.com/vercel/next.js/discussions/61549
if (reactPdf?.pdfjs && typeof window !== "undefined") {
  const workerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${reactPdf.pdfjs.version}/pdf.worker.min.mjs`;
  reactPdf.pdfjs.GlobalWorkerOptions.workerSrc = workerSrc;
}

// ...

// Use <reactPdf.Document {/*...*/} />

// ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests