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

JavaScript heap out of memory during next build (Next 15.1) #73793

Closed
pchab opened this issue Dec 11, 2024 · 12 comments · Fixed by #73908
Closed

JavaScript heap out of memory during next build (Next 15.1) #73793

pchab opened this issue Dec 11, 2024 · 12 comments · Fixed by #73908
Labels
bug Issue was opened via the bug report template.

Comments

@pchab
Copy link

pchab commented Dec 11, 2024

Link to the code that reproduces this issue

https://codesandbox.io/p/devbox/distracted-forest-vhlczj?workspaceId=ws_LGaEwH7iNTCahs4YK1orjP

To Reproduce

  1. Have a moderately complex page to build (generateStaticParams + parallel routes)
  2. next build
  3. process will crash JavaScript heap out of memory (usually during the 'Collecting page data' step)

Current vs. Expected behavior

next build process doesn't crash.

Provide environment information

Binaries:
  Node: 22.12.0
  npm: 10.9.0
  Yarn: 1.22.19
  pnpm: 8.10.2
Relevant Packages:
  next: 15.1.1-canary.0 // Latest available version is detected (15.1.1-canary.0).
  eslint-config-next: N/A
  react: 19.0.0
  react-dom: 19.0.0
  typescript: 5.3.3
Next.js Config:
  output: N/A


(Also tested with node 20)

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

Not sure

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

next build (local), Vercel (Deployed)

Additional context

The crash doesn't happen if I reduce the size of the array returned by generateStaticParams or if I remove a couple parallel routes. This happens on all platform I tested: Vercel, locally and on codesandbox.io.
This used to work correctly with [email protected].

@pchab pchab added the bug Issue was opened via the bug report template. label Dec 11, 2024
@icyJoseph
Copy link
Contributor

Hi, could you move this to a Stackblitz instead of codesandbox? Just type next.new into your browser address bar, and redo the example there.

@pchab
Copy link
Author

pchab commented Dec 12, 2024

https://stackblitz.com/edit/nextjs-vo1ozofj

@pchab
Copy link
Author

pchab commented Dec 12, 2024

On stackblitz, the build doesn't crash but never completes and stay indefinitely on the collecting page data step

@icyJoseph
Copy link
Contributor

I tried to use workers to get the build going, but...

uncaughtException [Error: Worker terminated due to reaching memory limit: JS heap out of memory] {
  code: 'ERR_WORKER_OUT_OF_MEMORY'
}

Otherwise, you get the:

<--- Last few GCs --->

[26998:0x138040000]    21645 ms: Scavenge 4051.8 (4120.5) -> 4049.6 (4123.7) MB, 6.1 / 0.0 ms  (average mu = 0.452, current mu = 0.157) allocation failure; 
[26998:0x138040000]    21653 ms: Scavenge 4055.0 (4123.7) -> 4052.4 (4142.0) MB, 6.5 / 0.0 ms  (average mu = 0.452, current mu = 0.157) allocation failure; 
[26998:0x138040000]    23958 ms: Mark-sweep 4065.0 (4142.0) -> 4056.9 (4150.2) MB, 2298.2 / 0.0 ms  (average mu = 0.253, current mu = 0.026) allocation failure; scavenge might not succeed


<--- JS stacktrace --->

And so on... so I guess, we have to debug the build script? 🤔

@icyJoseph
Copy link
Contributor

Alright, I've done a binary search/bisect, which ever, and found that this has been introduced at 15.0.4-canary.50:

Now it is time to check the changes introduced on that canary.

@icyJoseph
Copy link
Contributor

icyJoseph commented Dec 12, 2024

Now I've been able to make a build work, by locally checking out this commit, and building Next.js up to that point:

Which happens right before, this was merged #73358

Checking out, 92b3dd2, where #73358 sits, triggers the error, so in all likelihood, the error comes from that ISR fix for empty generateStaticParams

@icyJoseph
Copy link
Contributor

icyJoseph commented Dec 12, 2024

🤔

// Recursively process parallel routes
for (const parallelRouteKey in parallelRoutes) {
const parallelRoute = parallelRoutes[parallelRouteKey]
await processLoaderTree(parallelRoute, [...currentSegments])
}
}

Could that be the issue? But why, does it build up to 2x or so pages, but fails at several

It looks like the recursion does end... so nope

@icyJoseph
Copy link
Contributor

icyJoseph commented Dec 12, 2024

Alright, so I found the issue I think,

    { slug: 'grault' },  { slug: 'garply' },  { slug: 'waldo' },
    { slug: 'fred' },    { slug: 'plugh' },   { slug: 'xyzzy' },
    { slug: 'thud' },    { slug: 'foo2' },    { slug: 'bar2' },
    { slug: 'baz2' },
    ... 17210268 more items

Within, buildAppStaticPaths, at the builtRouteParams, that can't be right... some checks are failing to contain this madness

I noticed that your repository example, can handle up to 33 routes, 34 kills it... probably the Array at hand goes way over the Array max length and boom

@icyJoseph
Copy link
Contributor

Just to keep on dumping info, if I take again, 1ed81f7, and run a build, the max I see is 28 items in the parentsParams, whereas, taking the 92b3dd2, causes this massive growth

@icyJoseph
Copy link
Contributor

One more thing before signing off.

I think these lines:

if (name === PAGE_SEGMENT_KEY) {
segments.push(...currentSegments)
}

Are introducing a lot of repeated routes, which ends up with the,

async function builtRouteParams(
parentsParams: Params[] = [],
idx = 0
): Promise<Params[]> {
parentParams growing exponentially.

If I do this tweak:

    // If this is a page segment, we know we've reached a leaf node associated with the
    // page we're collecting segments for. We can add the collected segments to our final result.
    if (name === PAGE_SEGMENT_KEY) {
      currentSegments.forEach((seg) => {
        if (
          segments.some(
            (other) =>
              other.name === seg.name &&
              other.filePath === seg.filePath &&
              other.param === seg.param
          )
        ) {
          return
        }
        segments.push(seg)
      })
    }

Then things build fine.

@pchab
Copy link
Author

pchab commented Dec 13, 2024

@icyJoseph Thanks a lot for your investigation so far. To give you an order of magnitude, I noticed the issue on an application where generateStaticParams is an array of 14 elements with 7 parallel routes (in a dashboard like page).
So not a massive amount of static params.

@icyJoseph
Copy link
Contributor

@pchab yeah, I saw that it gets extremely bad very quickly with the number of parallel routes. This must be fixed soon!

ztanner added a commit that referenced this issue Dec 16, 2024
This refactors `collectAppPageSegments` to be more efficient especially
when dealing with multiple parallel routes, where the number of
combinations of segments can drastically increase how many segments are
returned per route. Previously this function only considered the
`children` parallel route. When it was updated to consider all possible
parallel routes in #73358, this had the unintended side effect of
increasing the amount of duplicate segments this function would have to
process.

We only need to return unique segments discovered from a particular
loader tree (representing a page), as opposed to the same segment as
many times as it might appear across all parallel routes. This PR uses a
map to track unique segments with a composite key used to identify the
discovered segments.

Additionally, this refactors the function to be iterative rather than
recursive. We keep track of a queue of segments to be processed (a tuple
of `[loaderTree, segments]`), and as we discover new parallel route
paths, we process the queue further.

Fixes #73793
Closes #73871 (h/t to @icyJoseph for identifying the cause of the memory
consumption)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Issue was opened via the bug report template.
Projects
None yet
2 participants