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

compat with react 19 #2756

Open
joacub opened this issue May 19, 2024 · 46 comments · May be fixed by #2783
Open

compat with react 19 #2756

joacub opened this issue May 19, 2024 · 46 comments · May be fixed by #2783

Comments

@joacub
Copy link

joacub commented May 19, 2024

React pdf is not compatible with react 19, will you make the library compat with this version ?

@joacub
Copy link
Author

joacub commented May 23, 2024

👀

@ZipBrandon
Copy link

Anything @eps1lon you can suggest? I have been poking around in the react-reconciler but it has drifted and largely undocumented.

@eps1lon
Copy link

eps1lon commented May 30, 2024

react-three-fiber also had to update its reconciler implementation. One of the breaking changes that was hard to track down was around commitUpdate which is no longer called with an updatePayload.

Where is the code for the reconciler implementation? Maybe something stands out to me. But no promises for now since we're not using that library at Vercel.

@ZipBrandon
Copy link

@eps1lon Here is where @diegomura creates the reconciler. When I updated the deps on my fork, I couldn't get the document to be created from the updateContainer call here

@joacub
Copy link
Author

joacub commented Jun 3, 2024

Some advances ?

@ZipBrandon
Copy link

FWIW I have patched my version to use React 18. I only use the node version of the renderer so having a version of react@18.3.1 that is only used for that suits my purpose.

I'm aliasing react-18 with "react-18": "npm:react@^18.3.1" in my package.json.

This uses a patch I made with patch-package where the renderer code thenimport React from "react-18".

I had to use the escape hatch of "use no memo" in my components to opt-out the compiler.

I use the pragma /** @jsxImportSource react-18 */ at the top of all files that are associated with the PDF rendering so it goes through the correct createElement().

That being said, this is just duct tape to get by until we have true React 19 compatability.

@cipriancaba
Copy link

That's a very interesting aproach @ZipBrandon but I feel like vercel and react will need to come up with a better solution than this since any 3rd party lib you might be using that is not updated will simply prevent you from upgrading to next 15 and react 19.

What is the suggested path here @eps1lon ? I've spent the time migrating to next 15, loved the wait everything felt until one of our agents told me the pdf generation page was buggered so had to revert to latest stable next

@cipriancaba
Copy link

Hi @ZipBrandon , do you mind sharing the patch you used to make this work?

@cipriancaba
Copy link

Thanks for the reply @eps1lon and your efforts are obviously much appreciated

I've been tracking your issue here eps1lon/react#10

I guess when I meant suggested path I meant more like this is literally a blocker for any kind of upgrade to react 19 / next 15. Is there potential for a more generic temporary fix (maybe similar to what @ZipBrandon suggested) until react 19 is fully adopted by lib authors? In every real life project there's this obscure library that you absolutely have to use and then if that blocks your entire upgrade path, you're basically stuck.

@ZipBrandon
Copy link

@cipriancaba Sure! Here's the the gist of the patch for @react-pdf/renderer https://gist.github.com/ZipBrandon/b6c3848a400feff9741d739b7544d4fe. This is only for rendering on the Node side. I didn't do anything for browser rendering.

Caveats to remember:

  • "react-18": "npm:react@^18.3.1", in your package.json deps.
  • /** @jsxImportSource react-18 */ at the top of any of your files that are React components that are being rendered in the PDF. You should not have React 19 and React 18 components in the same file.
  • Import anything you need in those components from React from "react-18" package.
  • If using the React Compiler, opt-out in the PDF components with "use no memo"

@alexandernanberg
Copy link

alexandernanberg commented Jun 12, 2024

I've done some investigations and the library is incompatible with react-reconciler 0.27.0 and later (React 19 requires ^0.31.0).

The library expect reconciler.createContainer to mutate the document property of the container.

const container = { type: 'ROOT', document: null };
renderer = renderer || createRenderer({ onChange });
const mountNode = renderer.createContainer(container);

which is then accessed here

const props = container.document.props || {};
const { pdfVersion, language, pageLayout, pageMode } = props;

In 0.27.0 the container.document is always null

I've tried finding workarounds, but I'm not familiar with the internals of the reconciler. @eps1lon thankful for any pointers!

Edit:

Seems like newer version of the reconciler forces the container to be immutable, or that it clones the object passed into createContainer. The mutation part is done in appendChildToContainer

appendChildToContainer(parentInstance, child) {
if (parentInstance.type === 'ROOT') {
parentInstance.document = child;
} else {
appendChild(parentInstance, child);
}
},

Edit 2:

Okay so looks like updateContainer now schedules an update instead of doing it sync. And library code relies on the sync behavior.

  const updateContainer = (doc, callback) => {
    renderer.updateContainer(doc, mountNode, null, callback);
  };

  if (initialValue) updateContainer(initialValue);

  setTimeout(() => {
    console.log(container);
	// container.document is set now
  }, 0);

@alexandernanberg alexandernanberg linked a pull request Jun 12, 2024 that will close this issue
@alexandernanberg
Copy link

alexandernanberg commented Jun 12, 2024

Got it working in #2783.

If you want to try it out early you can:

  1. Install @alexandernanberg/react-pdf-renderer
  2. Replace all references of @react-pdf/renderer with alexandernanberg/react-pdf-renderer

@karlhorky
Copy link
Contributor

karlhorky commented Jun 12, 2024

@alexandernanberg thanks for this!

Tried @alexandernanberg/react-pdf-renderer out just now and it works in some circumstances!

However, it is hanging with our Express app which uses:

  • custom fonts (this seems to be the problem)
    • Font.register() with src set to an https:// URL of a WOFF font
    • StyleSheet.create() with fontFamily set to a custom font which uses Font.register()
  • (await renderToStream()).pipe(response)

We do also have this pragma, not sure if that's relevant:

/** @jsxRuntime automatic @jsxImportSource react */

@alexandernanberg
Copy link

@karlhorky Custom fonts and stylesheets work for me, although I'm using Next.js.

I suspect that I broke something in this commit ed62fa6 (#2783). I'll see if I can revert those changes

@karlhorky
Copy link
Contributor

karlhorky commented Jun 13, 2024

Custom fonts and stylesheets work for me

Interesting, are you also using the same configuration?

  • Font.register() with src of:
    • https:// URL
    • WOFF font
  • StyleSheet.create() with fontFamily

If you could post an example of your code, then that would be helpful to track down working / not working configurations.

I'll see if I can revert those changes

Thanks!

@alexandernanberg
Copy link

Only .ttf fonts are supported AFAIK https://react-pdf.org/fonts

Example
import {
  Document,
  Font,
  Page,
  StyleSheet,
  View,
  renderToStream,
} from "@alexandernanberg/react-pdf-renderer";

const fontFamily = {
  sans: "Inter",
};

Font.register({
  family: fontFamily.sans,
  fonts: [
    {
      src: "https://storage.googleapis.com/.../fonts/Inter-Regular.ttf",
      fontWeight: "normal",
    },
  ],
});

export function renderCertificate() {
  return renderToStream(<Certificate />);
}

export function Certificate() {
  return (
    <Document>
      <Page style={styles.page}>
        <View style={styles.container}></View>
      </Page>
    </Document>
  );
}

const styles = StyleSheet.create({
  text: {
    fontFamily: fontFamily.sans,
    fontWeight: "normal",
    fontSize: 14,
  },
});

@karlhorky
Copy link
Contributor

Only .ttf fonts are supported AFAIK

WOFF fonts are also supported, this is just undocumented. I've opened a PR to document that here:

@karlhorky
Copy link
Contributor

karlhorky commented Jun 13, 2024

Interesting, WOFF is not the problem after all, this .woff font works from Google Fonts (Inter font):

Font.register({
  family: 'Inter',
  format: 'woff',
  src: 'https://fonts.gstatic.com/s/inter/v12/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuGKYAZ9hjp-Ek-_EeA.woff',
});

All fonts on Google Fonts seem to work.

It seems to be a problem with my own font files (both TTF and WOFF) being unusable in some way. Maybe something in some dependency is being more strict with certain font files.

@karlhorky
Copy link
Contributor

karlhorky commented Jun 13, 2024

Ohh looks like this hanging behavior is a known problem with custom fonts and react-pdf and fontkit (caused by the transitive dependency restructure@3.0.1):

Downgrading to restructure@3.0.0 as mentioned in the issues above resolves the issue.


Thus, I can confirm that @alexandernanberg/react-pdf-renderer@4.0.0-canary-2 is working with React 19. 🙌 🎉

@ZipBrandon
Copy link

I'm using it now too! Thanks so much @alexandernanberg!

@Timothylp
Copy link

Hello,

while trying with @alexandernanberg/react-pdf-renderer package, I can't get the PDF rendering to work, I still get the following error:

⨯ ..\node_modules\react-reconciler\cjs\react-reconciler.development.js (15700:59) @ module.exports
⨯ TypeError: Cannot read properties of undefined (reading 'S')
    at Results (./app/(frontend)/results/page.tsx:41:107)
digest: "3784830825"
  15698 |         _currentRenderer2: null
  15699 |       },
> 15700 |       prevOnStartTransitionFinish = ReactSharedInternals.S;
        |                                                           ^
  15701 |     ReactSharedInternals.S = function (transition, returnValue) {
  15702 |       "object" === typeof returnValue &&
  15703 |         null !== returnValue &&

Is it maybe because there was an update of the beta version of react 19?

@mamlzy
Copy link

mamlzy commented Oct 23, 2024

@alexandernanberg can you fix this bugs in your forked repo? https://github.com/diegomura/react-pdf/pull/2878/files

@alexandernanberg
Copy link

I've rebased on main and released a new 4.0.0-canary-3 version of @alexandernanberg/react-pdf-renderer. Haven't had time to test much yet so let me know if it's working as expected

@mamlzy
Copy link

mamlzy commented Oct 23, 2024

Thank you @alexandernanberg , for responding to my request. Now i go back to Next.js 14, which uses React 18. There is so much unsupported libraries in react 19, and your forked repo probably only work on react 18 right?, maybe I'll try it later on Next.js 15!

@karlhorky
Copy link
Contributor

karlhorky commented Oct 23, 2024

@mamlzy I think there are a few misconceptions here:

@mamlzy
Copy link

mamlzy commented Oct 23, 2024

@karlhorky i'm using next 15 app router not pages router so it will uses react 19, am i correct?

@Jussinevavuori
Copy link

Hello,

while trying with @alexandernanberg/react-pdf-renderer package, I can't get the PDF rendering to work, I still get the following error:

⨯ ..\node_modules\react-reconciler\cjs\react-reconciler.development.js (15700:59) @ module.exports
⨯ TypeError: Cannot read properties of undefined (reading 'S')
    at Results (./app/(frontend)/results/page.tsx:41:107)
digest: "3784830825"
  15698 |         _currentRenderer2: null
  15699 |       },
> 15700 |       prevOnStartTransitionFinish = ReactSharedInternals.S;
        |                                                           ^
  15701 |     ReactSharedInternals.S = function (transition, returnValue) {
  15702 |       "object" === typeof returnValue &&
  15703 |         null !== returnValue &&

Is it maybe because there was an update of the beta version of react 19?

Any update on this? I'm still experiencing this on @alexandernanberg/react-pdf-renderer@4.0.0-canary-3.

@alexandernanberg
Copy link

@Jussinevavuori Are you on Next.js 15 and React RC? My guess it's something with multiple or incorrect version of React

@Jussinevavuori
Copy link

Jussinevavuori commented Oct 25, 2024

@alexandernanberg My React versions are listed below

"dependencies": {
  "react": "19.0.0-rc-69d4b800-20241021",
  "react-dom": "19.0.0-rc-69d4b800-20241021",
  "next": "15.0.1"
},
"devDependencies": {
  "@types/react": "npm:types-react@19.0.0-rc.1",
  "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1"
}

@sam3d
Copy link

sam3d commented Oct 27, 2024

@alexandernanberg I'm also getting the same error on the same versions of the packages as @Jussinevavuori

@ringge
Copy link

ringge commented Oct 28, 2024

@alexandernanberg I'm also getting the same error on the same versions of the packages as @Jussinevavuori

same here

@jpainam
Copy link

jpainam commented Oct 31, 2024

@alexandernanberg I'm also getting the same error on the same versions of the packages as @Jussinevavuori

Same here, on react 19 and nextjs 15

@alexandernanberg
Copy link

Are you rendering the pdf client side? It's working for me server side with Nextjs 15

@jpainam
Copy link

jpainam commented Oct 31, 2024

Rendering server side. here is my code.

import type { NextRequest } from "next/server";

import { PdfExample, renderToStream } from "@repo/reports";

export async function GET(req: NextRequest) {
    const requestUrl = new URL(req.url);
    const stream = await renderToStream(
      await PdfExample(),
    );
    const blob = await new Response(stream).blob();

    const headers: Record<string, string> = {
      "Content-Type": "application/pdf",
      "Cache-Control": "no-store, max-age=0",
    };
    if (!preview) {
      headers["Content-Disposition"] =
        `attachment; filename="filename.pdf"`;
    }
    return new Response(blob, { headers });
}

renderToStream is exported from @alexandernanberg/react-pdf-renderer

I'm getting this error

ypeError: Cannot read properties of undefined (reading 'S')
    at module.exports (//.next/server/chunks/08b5e_@alexandernanberg_react-pdf-renderer_lib_react-pdf_1362ba.js:14494:67)
    at createRenderer /.next/server/chunks/08b5e_@alexandernanberg_react-pdf-renderer_lib_react-pdf_1362ba.js:14980:12)
    at pdf (/.next/server/chunks/08b5e_@alexandernanberg_react-pdf-renderer_lib_react-pdf_1362ba.js:15180:28)
    at renderToStream (/.next/server/chunks/08b5e_@alexandernanberg_react-pdf-renderer_lib_react-pdf_1362ba.js:15311:22)
    at GET (/.next/server/chunks/[root of the server]__03a291._.js:10336:252)
    at async AppRouteRouteModule.do (//next/dist/compiled/next-server/app-route.runtime.dev.js:10:32801)
    at async AppRouteRouteModule.handle (/node_modules/next/dist/compiled/next-server/app-route.runtime.dev.js:10:38302)

@sam3d
Copy link

sam3d commented Oct 31, 2024

@alexandernanberg any chance you could share your working project's lockfile? I have a suspicion there is some kind of really specific dependency tree required to make this work properly

@alexandernanberg
Copy link

I'm able to reproduce the issue outside my actual app https://github.com/alexandernanberg/react-pdf-renderer-nextjs , I'll look into it whenever I get time. Can't make any promises.

I can't share the whole lockfile but I'm using pnpm and I've locked the React versions to these + nextjs@15.0.1

catalogs:
  react19:
    react: 19.0.0-rc-69d4b800-20241021
    react-dom: 19.0.0-rc-69d4b800-20241021
    '@types/react': npm:types-react@rc
    '@types/react-dom': npm:types-react-dom@rc

Tried changing to those in my repro but still got the same issue. We might have to inline to react-reconciler into the renderer package instead of using it as a dep

@sam3d
Copy link

sam3d commented Nov 1, 2024

AFAICT react-reconciler is already inlined into the render package? The entire source of react-reconciler.production.js is present in the resulting outputs of react-pdf-renderer/lib

@alexandernanberg
Copy link

You're right, had a brief experiment keeping it as a dep but removed that later 7c24f14.

So digging into this a bit more, React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE is undefined which the reconciler depends on.

There is however React.__SERVER_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, so looks like the React pkg adapts based on the env 🤔

@diegopluna
Copy link

Rendering server side. here is my code.

import type { NextRequest } from "next/server";

import { PdfExample, renderToStream } from "@repo/reports";

export async function GET(req: NextRequest) {
    const requestUrl = new URL(req.url);
    const stream = await renderToStream(
      await PdfExample(),
    );
    const blob = await new Response(stream).blob();

    const headers: Record<string, string> = {
      "Content-Type": "application/pdf",
      "Cache-Control": "no-store, max-age=0",
    };
    if (!preview) {
      headers["Content-Disposition"] =
        `attachment; filename="filename.pdf"`;
    }
    return new Response(blob, { headers });
}

renderToStream is exported from @alexandernanberg/react-pdf-renderer

I'm getting this error

ypeError: Cannot read properties of undefined (reading 'S')
    at module.exports (//.next/server/chunks/08b5e_@alexandernanberg_react-pdf-renderer_lib_react-pdf_1362ba.js:14494:67)
    at createRenderer /.next/server/chunks/08b5e_@alexandernanberg_react-pdf-renderer_lib_react-pdf_1362ba.js:14980:12)
    at pdf (/.next/server/chunks/08b5e_@alexandernanberg_react-pdf-renderer_lib_react-pdf_1362ba.js:15180:28)
    at renderToStream (/.next/server/chunks/08b5e_@alexandernanberg_react-pdf-renderer_lib_react-pdf_1362ba.js:15311:22)
    at GET (/.next/server/chunks/[root of the server]__03a291._.js:10336:252)
    at async AppRouteRouteModule.do (//next/dist/compiled/next-server/app-route.runtime.dev.js:10:32801)
    at async AppRouteRouteModule.handle (/node_modules/next/dist/compiled/next-server/app-route.runtime.dev.js:10:38302)

How are you not getting the:

Argument of type 'ReadableStream' is not assignable to parameter of type 'BodyInit | null | undefined'.ts(2345)

Typescript error on the blob definition?

@jpainam
Copy link

jpainam commented Nov 5, 2024

Rendering server side. here is my code.

import type { NextRequest } from "next/server";

import { PdfExample, renderToStream } from "@repo/reports";

export async function GET(req: NextRequest) {
    const requestUrl = new URL(req.url);
    const stream = await renderToStream(
      await PdfExample(),
    );
    const blob = await new Response(stream).blob();

    const headers: Record<string, string> = {
      "Content-Type": "application/pdf",
      "Cache-Control": "no-store, max-age=0",
    };
    if (!preview) {
      headers["Content-Disposition"] =
        `attachment; filename="filename.pdf"`;
    }
    return new Response(blob, { headers });
}

renderToStream is exported from @alexandernanberg/react-pdf-renderer
I'm getting this error

ypeError: Cannot read properties of undefined (reading 'S')
    at module.exports (//.next/server/chunks/08b5e_@alexandernanberg_react-pdf-renderer_lib_react-pdf_1362ba.js:14494:67)
    at createRenderer /.next/server/chunks/08b5e_@alexandernanberg_react-pdf-renderer_lib_react-pdf_1362ba.js:14980:12)
    at pdf (/.next/server/chunks/08b5e_@alexandernanberg_react-pdf-renderer_lib_react-pdf_1362ba.js:15180:28)
    at renderToStream (/.next/server/chunks/08b5e_@alexandernanberg_react-pdf-renderer_lib_react-pdf_1362ba.js:15311:22)
    at GET (/.next/server/chunks/[root of the server]__03a291._.js:10336:252)
    at async AppRouteRouteModule.do (//next/dist/compiled/next-server/app-route.runtime.dev.js:10:32801)
    at async AppRouteRouteModule.handle (/node_modules/next/dist/compiled/next-server/app-route.runtime.dev.js:10:38302)

How are you not getting the:

Argument of type 'ReadableStream' is not assignable to parameter of type 'BodyInit | null | undefined'.ts(2345)

Typescript error on the blob definition?

It works at runtime. just add // @ts-expect-error TODO: WTF Typescript right before const blob = await new Response(stream).blob();

This is due to TypeScript not recognizing ReadableStream as a valid arg to Response. I really don't know why.

BTW, it also works if you stream directly.

@diegopluna read https://midday.ai/updates/invoice-pdf
This was working fine before react 19 and nextjs 15

@diegopluna
Copy link

Rendering server side. here is my code.

import type { NextRequest } from "next/server";

import { PdfExample, renderToStream } from "@repo/reports";

export async function GET(req: NextRequest) {
    const requestUrl = new URL(req.url);
    const stream = await renderToStream(
      await PdfExample(),
    );
    const blob = await new Response(stream).blob();

    const headers: Record<string, string> = {
      "Content-Type": "application/pdf",
      "Cache-Control": "no-store, max-age=0",
    };
    if (!preview) {
      headers["Content-Disposition"] =
        `attachment; filename="filename.pdf"`;
    }
    return new Response(blob, { headers });
}

renderToStream is exported from @alexandernanberg/react-pdf-renderer
I'm getting this error

ypeError: Cannot read properties of undefined (reading 'S')
    at module.exports (//.next/server/chunks/08b5e_@alexandernanberg_react-pdf-renderer_lib_react-pdf_1362ba.js:14494:67)
    at createRenderer /.next/server/chunks/08b5e_@alexandernanberg_react-pdf-renderer_lib_react-pdf_1362ba.js:14980:12)
    at pdf (/.next/server/chunks/08b5e_@alexandernanberg_react-pdf-renderer_lib_react-pdf_1362ba.js:15180:28)
    at renderToStream (/.next/server/chunks/08b5e_@alexandernanberg_react-pdf-renderer_lib_react-pdf_1362ba.js:15311:22)
    at GET (/.next/server/chunks/[root of the server]__03a291._.js:10336:252)
    at async AppRouteRouteModule.do (//next/dist/compiled/next-server/app-route.runtime.dev.js:10:32801)
    at async AppRouteRouteModule.handle (/node_modules/next/dist/compiled/next-server/app-route.runtime.dev.js:10:38302)

How are you not getting the:

Argument of type 'ReadableStream' is not assignable to parameter of type 'BodyInit | null | undefined'.ts(2345)

Typescript error on the blob definition?

It works at runtime. just add // @ts-expect-error TODO: WTF Typescript right before const blob = await new Response(stream).blob();

This is due to TypeScript not recognizing ReadableStream as a valid arg to Response. I really don't know why.

BTW, it also works if you stream directly.

@diegopluna read https://midday.ai/updates/invoice-pdf This was working fine before react 19 and nextjs 15

Thanks, probabily will downgrade my project to next 14 while this isnt resolved!

@eps1lon
Copy link

eps1lon commented Nov 6, 2024

For people using @alexandernanberg/react-pdf-renderer:
Is it only breaking in Next.js route handlers and React Server components or is it also breaking in Client components. Would be helpful to verify if adding use client at the top helps. This doesn't work for Next.js Route handlers but pages can leverage use client.

@alexandernanberg
Copy link

@eps1lon AFAICT it's breaking only in Server component and route handlers. E.g. this is working https://github.com/alexandernanberg/react-pdf-renderer-nextjs/blob/main/src/app/page.tsx as expected.

Any plans on supporting use client for route handlers, or other ways to opt out of Server components?

@eps1lon
Copy link

eps1lon commented Nov 7, 2024

How does this work for a page? Isn't the server use case to just save it as a file i.e. only relevant for API endpoints?

There's currently no plan to support Client components in Next.js Route handlers. You can still use pages/api though in Next.js which does support Client components (but no React Server Components).

@alexandernanberg
Copy link

I think a common use case is to have a route handler generate the PDF and then use https://github.com/wojtekmaj/react-pdf to display it (by just giving it the endpoint URL). That way you don't need to handle the display and download cases differently, it's just a single endpoint that covers everything.

Got it. Haven't really looked into it, but does react-reconciler work in Server components? Just thinking if it's worth investigating if we could add Server component support. I suspect the majority of users are using Route handlers atm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.