vercel / next.js

The React Framework
https://nextjs.org
MIT License
126.43k stars 26.91k forks source link

RangeError: Maximum call stack size exceeded after enabling turbopack in dev #71878

Open nphmuller opened 21 hours ago

nphmuller commented 21 hours ago

Link to the code that reproduces this issue

https://github.com/nphmuller/next15-turbo-maxstack-repro

To Reproduce

  1. Clone repro
  2. npm run dev
  3. Open localhost:3000
  4. The page renders properly, but in the console you see RangeError: Maximum call stack size exceeded.

If you remove --turbo in package.json there is no error.

Main repro: https://github.com/nphmuller/next15-turbo-maxstack-repro/blob/master/app/wrapper.tsx

Manual repro:

  1. Create wrapper.tsx:
"use client";

const Component = () => <div>Stub component</div>;

// This line runs fine without turbopack and causes an infinite loop with turbopack
const _cloned = deepClone(<Component />);

// If we don't render anything and just import for the side effect, the error doesn't happen
// For the error to happen we need to import this wrapper and render it somewhere
export default function Wrapper({ children }: { children: React.ReactNode }) {
  return <>{children}</>;
}

// ----- the code below is taken from the MUI source and it's how they implemented deepClone -----
// see: https://github.com/mui/material-ui/blob/2359c0badfb5a8d746ccc83c1d082c4ad70a542b/packages/mui-utils/src/deepmerge/deepmerge.ts

function deepClone<T>(source: T): T {
  if (!isPlainObject(source)) {
    return source;
  }

  const output: Record<any, any> = {};

  Object.keys(source).forEach((key) => {
    if (key === "_owner") {
      // !!!!! Here's the difference between webpack and turbopack !!!!!
      // !!!!! In webpack the value of _owner is null !!!!!
      // !!!!! In turbopack the value of _owner is not null !!!!
      console.log("key " + key + " is null " + source[key] === null);
      console.log(source[key]);
    }
    output[key] = deepClone(source[key]);
  });

  return output;
}

// https://github.com/sindresorhus/is-plain-obj/blob/main/index.js
function isPlainObject(item: unknown): item is Record<keyof any, unknown> {
  if (typeof item !== "object" || item === null) {
    return false;
  }

  const prototype = Object.getPrototypeOf(item);

  const res =
    (prototype === null ||
      prototype === Object.prototype ||
      Object.getPrototypeOf(prototype) === null) &&
    !(Symbol.toStringTag in item) &&
    !(Symbol.iterator in item);

  return res;
}
  1. Use wrapper.tsx in either layout.tsx or page.tsx (doesn't matter which one):

layout.tsx:

import Wrapper from "./wrapper";

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body>
        <Wrapper>{children}</Wrapper>
      </body>
    </html>
  );
}

Current vs. Expected behavior

Expected: No error

Current: Error in console. Full stack trace:

⨯ RangeError: Maximum call stack size exceeded
    at isPlainObject (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:376:23)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:384:10)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)
    at /Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:389:23
    at Array.forEach (<anonymous>)
    at deepClone (/Users/nphmuller/Dev/_tests/next-15-turbo-mui/.next/server/chunks/ssr/node_modules_70250b._.js:388:25)

Provide environment information

❯ npx next info

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 24.0.0: Mon Aug 12 20:51:54 PDT 2024; root:xnu-11215.1.10~2/RELEASE_ARM64_T6000
  Available memory (MB): 32768
  Available CPU cores: 10
Binaries:
  Node: 20.13.0
  npm: 10.8.3
  Yarn: N/A
  pnpm: 9.6.0
Relevant Packages:
  next: 15.0.1 // Latest available version is detected (15.0.1).
  eslint-config-next: 15.0.1
  react: 19.0.0-rc-69d4b800-20241021
  react-dom: 19.0.0-rc-69d4b800-20241021
  typescript: 5.6.3
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

Turbopack

Which stage(s) are affected? (Select all that apply)

next dev (local)

Additional context

I also tested against the latest preview release (15.0.2-canary.7) and the issue still repros.

It seems that the value of the _owner key of the to be cloned component has a different value between webpack (null) and turbopack (see below). The value in turbopack seems to cause an infinite loop in the deepClone code.

The value of _owner when ran via turbopack is:

{
  parent: {
    parent: { parent: [Object], type: 'html', owner: [Object], stack: null },
    type: 'body',
    owner: {
      name: 'RootLayout',
      env: 'Server',
      key: null,
      owner: null,
      props: [Object]
    },
    stack: null
  },
  type: {
    '$$typeof': Symbol(react.lazy),
    _payload: Promise {
      status: 'resolved_module',
      value: [Array],
      reason: null,
      _response: [ResponseInstance],
      _debugInfo: []
    },
    _init: [Function: readChunk],
    _debugInfo: []
  },
  owner: {
    name: 'RootLayout',
    env: 'Server',
    key: null,
    owner: null,
    props: { children: [Object], params: [Getter] }
  },
  stack: null
}

While the repro looks pretty exotic, it's mainly there to have an example without any external dependencies.

Here's a more practical example of the code that originally caused the error: https://github.com/nphmuller/next15-turbo-maxstack-repro/blob/0d1bfdfbc86441fc0603c151e36ecfac4892ace9/app/wrapper.tsx

gruckion commented 20 hours ago

Can you apply the binary search approach to debugging. Take your root page / layout and make them just show basic “hello” and “world”. The issue should go away.

then reintroduce the code bit by bit. So introduce your root layout but keep the page as just “world”. This will allow you to determine if the issue exists in the layout or in the page / the nested pages.

Slowly uncomment parts of your code bit by bit until the issue comes back. Then you can identify what erroneous code is causing the issue.

Turbopack works fine for me. But when I first tried to set it up I had to do the above approach to figure out what was causing me issues.

I hope this advice helps. It can be applied to many problems.

https://www.codewithjason.com/binary-search-debugging/

nphmuller commented 19 hours ago

You’re totally right and I should have explained this better. I tried to follow this approach when making the repro, and the part that is causing the error is adding <InfoSharp /> (or any other icon from the library) at that specific place in createTheme(). When I render the icon directly in my component, instead of referencing it in createTheme(), the error doesn't happen.

When I’m at my pc again I’ll edit my post and look further. But I should have at least mentioned this before in my repro…

I’ll also try to repro this issue in plain code without using MUI, but I haven’t be able to so far.

nphmuller commented 18 hours ago

Rewrote the repro from scratch, without any dependencies. Let me know if I can do more to clarify! :)