opennextjs / opennextjs-netlify

Open Next.js adapter for Netlify
https://opennext.js.org/netlify
678 stars 87 forks source link

[next@15] Undocumented `reactMaxHeadersLength` next config option is not respected #2626

Open serhalp opened 2 months ago

serhalp commented 2 months ago

next@v15.0.0-canary.99 added a new reactMaxHeadersLength option to next.config.js.

It was introduced in this PR: https://github.com/vercel/next.js/pull/67715. This gets passed to react-dom's renderToReadableStream here: https://github.com/vercel/next.js/blob/4837a67fb9bc7199e48cd8bd2cc42659b17dfacc/packages/next/src/server/app-render/app-render.tsx#L1490.

This was added in https://github.com/facebook/react/pull/27641.

It ultimately gets used here via remainingCapacity.

One theory is that this functionality doesn't play nicely with our use of a TransformStream: https://github.com/netlify/next-runtime/blob/f10d6611921fe355f33804f394eb25678cbedd85/src/run/handlers/server.ts#L135.

FAIL test/e2e/app-dir/react-max-headers-length/react-max-headers-length.test.ts (296.858 s)
  react-max-headers-length
    reactMaxHeadersLength = 0
      ✕ should respect reactMaxHeadersLength (288 ms)
    reactMaxHeadersLength = 400
      ✕ should respect reactMaxHeadersLength (277 ms)
    reactMaxHeadersLength = undefined
      ✓ should respect reactMaxHeadersLength (247 ms)
    reactMaxHeadersLength = 10000
      ✕ should respect reactMaxHeadersLength (278 ms)

  ● react-max-headers-length › reactMaxHeadersLength = 0 › should respect reactMaxHeadersLength

    expect(received).toBeNull()

    Received: "</?q=some+string+that+spans+lots+of+characters&i=00>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=01>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=02>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=03>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=04>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=05>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=06>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=07>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=08>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=09>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=10>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=11>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=12>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=13>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=14>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=15>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=16>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=17>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=18>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=19>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=20>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=21>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=22>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=23>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=24>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=25>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=26>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=27>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=28>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=29>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=30>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=31>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=32>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=33>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=34>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=35>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=36>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=37>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=38>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=39>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=40>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=41>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=42>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=43>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=44>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=45>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=46>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=47>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=48>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=49>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=50>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=51>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\", </?q=some+string+that+spans+lots+of+characters&i=52>; rel=preload; as=\"font\"; crossorigin=\"\"; type=\"font/woff2\""

      46 |         } else if (reactMaxHeadersLength === 0) {
      47 |           // This is the case where the header is not emitted.
    > 48 |           expect(header).toBeNull()
         |                          ^
      49 |         } else if (typeof reactMaxHeadersLength === 'number') {
      50 |           // This is the case where the header is emitted and the length is
      51 |           // respected.

      at Object.toBeNull (e2e/app-dir/react-max-headers-length/react-max-headers-length.test.ts:48:26)

  ● react-max-headers-length › reactMaxHeadersLength = 400 › should respect reactMaxHeadersLength

    expect(received).toBeLessThanOrEqual(expected)

    Expected: <= 400
    Received:    5987

      56 |             calculateMinHeaderLength(reactMaxHeadersLength)
      57 |           )
    > 58 |           expect(header.length).toBeLessThanOrEqual(reactMaxHeadersLength)
         |                                 ^
      59 |         }
      60 |       })
      61 |     }

      at Object.toBeLessThanOrEqual (e2e/app-dir/react-max-headers-length/react-max-headers-length.test.ts:58:33)

  ● react-max-headers-length › reactMaxHeadersLength = 10000 › should respect reactMaxHeadersLength

    expect(received).toBeGreaterThanOrEqual(expected)

    Expected: >= 9942
    Received:    5987

      53 |           expect(header).toBeString()
      54 |
    > 55 |           expect(header.length).toBeGreaterThanOrEqual(
         |                                 ^
      56 |             calculateMinHeaderLength(reactMaxHeadersLength)
      57 |           )
      58 |           expect(header.length).toBeLessThanOrEqual(reactMaxHeadersLength)

      at Object.toBeGreaterThanOrEqual (e2e/app-dir/react-max-headers-length/react-max-headers-length.test.ts:55:33)

Test Suites: 1 failed, 1 total
Tests:       3 failed, 1 passed, 4 total

Data

The following is parsed automatically by the Next.js repo e2e test report generator.

test: test/e2e/app-dir/react-max-headers-length/react-max-headers-length.test.ts reason: Undocumented next@15-canary reactMaxHeadersLength config option is not respected; cannot debug due to React 19 RC being closed source