c5a8e0989e
In the current version of Next.js there are 4 processes when running in production: - Server - Routing - Rendering Pages Router - Rendering App Router This setup was introduced in order to allow App Router and Pages Router to use different versions of React (i.e. Server Actions currently requires react@experimental to function). I wrote down more on why these processes exist in this comment: https://github.com/vercel/next.js/issues/49929#issuecomment-1637185156 This PR combines the Server and Routing process into one handler, as the "Server" process was only proxying to the Routing process. In my testing this caused about ~2x the amount of memory per request as the response body was duplicated between the processes. This was especially visible in the case of that memory leak in Node.js 18.16 as it grew memory usage on both sides quickly. In the process of going through these changes I found a couple of other bugs like the propagation of values to the worker processes not being awaited ([link](https://github.com/vercel/next.js/pull/53523/files#diff-0ef09f360141930bb03263b378d37d71ad9432ac851679aeabc577923536df84R54)) and the dot syntax for propagation was not functioning. It also seemed there were a few cases where watchpack was used that would cause many more files to be watched than expected, for now I've removed those cases, specifically the "delete dir while running" and instrument.js hmr (instrument.js is experimental). Those tests have been skipped for now until we can revisit them to verfiy it I've also cleaned up the types a bit while I was looking into these changes. ### Improvement ⚠️ Important preface to this, measuring memory usage / peak usage is not super reliable especially when JS gets garbage collected. These numbers are just to show the rough percentage of less memory usage. #### Baseline Old: ``` next-server: 44.8MB next-router-worker: 57.5MB next-render-worker-app: 39,6MB next-render-worker-pages: 39,1MB ``` New: ``` next-server: Removed next-router-worker: 64.4MB next-render-worker-app: 43.1MB (Note: no changes here, this shows what I meant by rough numbers) next-render-worker-pages: 42.4MB (Note: no changes here, this shows what I meant by rough numbers) ``` Overall win: ~40MB (process is removed) #### Peak usage Old: ``` next-server: 118.6MB next-router-worker: 223.7MB next-render-worker-app: 32.8MB (I ran the test on a pages application in this case) next-render-worker-pages: 101.1MB ``` New: ``` next-server: Removed next-router-worker: 179.1MB next-render-worker-app: 33.4MB next-render-worker-pages: 117.5MB ``` Overall win: ~100MB (but it scales with requests so it was about ~50% of next-router-worker constantly) Related: #53523 --------- Co-authored-by: JJ Kasper <jj@jjsweb.site>
136 lines
4.2 KiB
TypeScript
136 lines
4.2 KiB
TypeScript
import { createNextDescribe } from 'e2e-utils'
|
|
import { check } from 'next-test-utils'
|
|
import path from 'path'
|
|
|
|
const describeCase = (
|
|
caseName: string,
|
|
callback: Parameters<typeof createNextDescribe>[2]
|
|
) => {
|
|
createNextDescribe(
|
|
caseName,
|
|
{
|
|
files: path.join(__dirname, caseName),
|
|
nextConfig: {
|
|
experimental: {
|
|
instrumentationHook: true,
|
|
},
|
|
},
|
|
skipDeployment: true,
|
|
},
|
|
callback
|
|
)
|
|
}
|
|
describe('Instrumentation Hook', () => {
|
|
// TODO: investigate the failure with esm import
|
|
// createNextDescribe(
|
|
// 'with-esm-import',
|
|
// {
|
|
// files: path.join(__dirname, 'with-esm-import'),
|
|
// nextConfig: {
|
|
// experimental: {
|
|
// instrumentationHook: true,
|
|
// },
|
|
// },
|
|
// dependencies: {
|
|
// // This test is mostly for compatibility with this package
|
|
// '@vercel/otel': 'latest',
|
|
// },
|
|
// skipDeployment: true,
|
|
// },
|
|
// ({ next }) => {
|
|
// eslint-disable-next-line jest/no-commented-out-tests
|
|
// it('with-esm-import should run the instrumentation hook', async () => {
|
|
// await next.render('/')
|
|
// await check(
|
|
// () => next.cliOutput,
|
|
// /register in instrumentation\.js is running/
|
|
// )
|
|
// })
|
|
// }
|
|
// )
|
|
|
|
describeCase('with-middleware', ({ next }) => {
|
|
it('with-middleware should run the instrumentation hook', async () => {
|
|
await next.render('/')
|
|
await check(() => next.cliOutput, /instrumentation hook on the edge/)
|
|
})
|
|
})
|
|
|
|
describeCase('with-edge-api', ({ next }) => {
|
|
it('with-edge-api should run the instrumentation hook', async () => {
|
|
await next.render('/api')
|
|
await check(() => next.cliOutput, /instrumentation hook on the edge/)
|
|
})
|
|
})
|
|
|
|
describeCase('with-edge-page', ({ next }) => {
|
|
it('with-edge-page should run the instrumentation hook', async () => {
|
|
await next.render('/')
|
|
await check(() => next.cliOutput, /instrumentation hook on the edge/)
|
|
})
|
|
})
|
|
|
|
describeCase('with-node-api', ({ next }) => {
|
|
it('with-node-api should run the instrumentation hook', async () => {
|
|
await next.render('/api')
|
|
await check(() => next.cliOutput, /instrumentation hook on nodejs/)
|
|
})
|
|
})
|
|
|
|
describeCase('with-node-page', ({ next }) => {
|
|
it('with-node-page should run the instrumentation hook', async () => {
|
|
await next.render('/')
|
|
await check(() => next.cliOutput, /instrumentation hook on nodejs/)
|
|
})
|
|
})
|
|
|
|
describeCase('with-async-node-page', ({ next }) => {
|
|
it('with-async-node-page should run the instrumentation hook', async () => {
|
|
const page = await next.render('/')
|
|
expect(page).toContain('Node - finished: true')
|
|
})
|
|
})
|
|
|
|
describeCase('with-async-edge-page', ({ next }) => {
|
|
it('with-async-edge-page should run the instrumentation hook', async () => {
|
|
const page = await next.render('/')
|
|
expect(page).toContain('Edge - finished: true')
|
|
})
|
|
})
|
|
|
|
describeCase('general', ({ next, isNextDev }) => {
|
|
it('should not overlap with a instrumentation page', async () => {
|
|
const page = await next.render('/instrumentation')
|
|
expect(page).toContain('Hello')
|
|
})
|
|
if (isNextDev) {
|
|
// TODO: Implement handling for changing the instrument file.
|
|
it.skip('should reload the server when the instrumentation hook changes', async () => {
|
|
await next.render('/')
|
|
await next.patchFile(
|
|
'./instrumentation.js',
|
|
`export function register() {console.log('toast')}`
|
|
)
|
|
await check(() => next.cliOutput, /toast/)
|
|
await next.renameFile(
|
|
'./instrumentation.js',
|
|
'./instrumentation.js.bak'
|
|
)
|
|
await check(
|
|
() => next.cliOutput,
|
|
/The instrumentation file has been removed/
|
|
)
|
|
await next.patchFile(
|
|
'./instrumentation.js.bak',
|
|
`export function register() {console.log('bread')}`
|
|
)
|
|
await next.renameFile(
|
|
'./instrumentation.js.bak',
|
|
'./instrumentation.js'
|
|
)
|
|
await check(() => next.cliOutput, /The instrumentation file was added/)
|
|
await check(() => next.cliOutput, /bread/)
|
|
})
|
|
}
|
|
})
|
|
})
|