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

Add _document and _app pre-import #23261

Merged
merged 6 commits into from Feb 10, 2022
Merged

Conversation

gwer
Copy link
Contributor

@gwer gwer commented Mar 22, 2021

This is an alternative to #23196 without pages pre-warm.

At #23187 @timneutkens disagreed with the need to warm up the pages. I think this issue requires additional discussion, but _app and _document can be warmed up right now.

@ijjk

This comment has been minimized.

Copy link
Member

@ijjk ijjk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, I'm gonna close this as it seems like further is discussion is needed/benchmarking done to see if this has any impact. Please open a discussion to continue investigation!

@ijjk ijjk closed this Jul 21, 2021
@gwer
Copy link
Contributor Author

gwer commented Jul 21, 2021

I don't think we need further discussion on this issue. There is no slowdown because _app and _document are loaded for every page.

@gwer gwer mentioned this pull request Jul 21, 2021
@ijjk
Copy link
Member

ijjk commented Jul 21, 2021

There is the case of API routes which have no need for _app and _document being loaded, also automatically statically optimized pages and prerendered pages don't need it to be loaded before they can be served, will re-open but continue discussion on the issue as we usually discuss changes like this before a PR to prevent the PR author from having to keep up with canary

@ijjk ijjk reopened this Jul 21, 2021
@abriginets
Copy link

abriginets commented Nov 4, 2021

I think the bigger question here is should I get billed on Vercel for NextJS wasting 10 seconds to additionaly compile _app and _document for pages I defined to be SSR. From my perspective as an everyday NextJS user I should not deal with framework issues and focus on building things I want. I should not waste my time reading issues, pull requests and source code just to know why I'm paying more than I have to. I'm really surprised and shocked honestly that NextJS compiles additional code in production runtime causing very long TTFB. That hurts my SEO and UX. @gwer is that possible to make _app and _document pre-import mandatory for pages where getServerSideProps is exported so that we can have a chance of merging it soon?

@gwer
Copy link
Contributor Author

gwer commented Jan 15, 2022

Oh, sorry @abriginets for long time without answer.

An opt-in configuration option has been added in this PR to allow developers to make decisions about whether to pre-import or not. And it's not enough to minimize TTFB, because there are only _app and _documents imported before server starts listening. For better optimization you need pre-import pages modules too. Something like this :: https://github.com/gwer/next-js-initial-freeze-workaround/blob/master/pages-preimport.js.

But such a workaround is slow down the whole server start. And I see the only one way to get a fast start for statically optimized pages and slow start for low TTFB with SSR. This way is to use two different servers: one for statically optimized pages and other for SSR.

But you should keep in mind that all my decisions and suggestions are about standalone deployment, not in Vercel. I can contribute only to free open source part. Not to a third-party cloud platform.

@timneutkens
Copy link
Member

I think the bigger question here is should I get billed on Vercel for NextJS wasting 10 seconds to additionaly compile _app and _document for pages I defined to be SSR. From my perspective as an everyday NextJS user I should not deal with framework issues and focus on building things I want. I should not waste my time reading issues, pull requests and source code just to know why I'm paying more than I have to. I'm really surprised and shocked honestly that NextJS compiles additional code in production runtime causing very long TTFB.

Just to be clear, Next.js does not ship the compilation to production, there is no bundling or anything you see in dev to compile code on the fly. The only thing that happens is that _app and _document are require()ed at runtime, same as page code, same as node_modules. There is no "10 seconds additional compile", that can only be caused by having top-level module code in your application that takes extremely long to run.

Copy link
Member

@timneutkens timneutkens left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pre-requireing the _app and _document is probably fine for the standalone mode @ijjk. I think this PR is fine to land without experimental opt-in if it checks for minimalMode, especially now that the target option is deprecated we can make improvements like these.

Just to be clear to future readers: "precompiling" here refers to requiring the code early which runs the top-level module code for _app and _document. It has nothing to do with compiling/bundling the files (e.g. through webpack).

@abriginets
Copy link

abriginets commented Jan 17, 2022

I think the bigger question here is should I get billed on Vercel for NextJS wasting 10 seconds to additionaly compile _app and _document for pages I defined to be SSR. From my perspective as an everyday NextJS user I should not deal with framework issues and focus on building things I want. I should not waste my time reading issues, pull requests and source code just to know why I'm paying more than I have to. I'm really surprised and shocked honestly that NextJS compiles additional code in production runtime causing very long TTFB.

Just to be clear, Next.js does not ship the compilation to production, there is no bundling or anything you see in dev to compile code on the fly. The only thing that happens is that _app and _document are require()ed at runtime, same as page code, same as node_modules. There is no "10 seconds additional compile", that can only be caused by having top-level module code in your application that takes extremely long to run.

@timneutkens for some reason Next.js deployed to Vercel was rendering every SSR page in my project for 10-15 seconds each time lambda was getting cold. I decided to move my project to Heroku (non-serverless env) and all those long cold starts stopped. If I redeploy my app to Heroku (which mean purging all Next.js cache) right now and try to hit any of the SSR page it would load for 2-3 seconds for the first time and then there will be no delays whatsoever for any subsequent request. I figured out that serverless container cold starts was not an issue (been monitoring logs for quite some time) as it always took no more than 400ms. For me it looks like Next.js is missing something in serverless env (something that is being persisted at the filesystem on Heroku or any other traditional hosting) and it needs to generate some data, or some cache, etc. before it could execute backend logic and serve html to users. I've been trying to dig up at least something but I couldn't find anything so I assumed that it might be dynamic imports, requires, etc. This leads me into thinking that Vercel is not an option for hosting SSR/ISR-based Next.js apps at least for now.

@leerob
Copy link
Member

leerob commented Jan 18, 2022

Next.js deployed to Vercel was rendering every SSR page in my project for 10-15 seconds each time lambda was getting cold.

Without a reproducible URL, it's hard to validate or verify this - but 10-15 is definitely not a lambda cold start IMO.

I figured out that serverless container cold starts was not an issue (been monitoring logs for quite some time) as it always took no more than 400ms.

This sounds more in line with cold start times I've seen (400ms).

This leads me into thinking that Vercel is not an option for hosting SSR/ISR-based Next.js apps at least for now.

One of the difficult parts about being a hosting platform is that there's only so much optimization you can do for user code. So in some instances, if code is written that takes 10s to fetch from an external API, there's not much you can do about it from the Vercel side.

There are many, many SSR / ISR Next.js applications on Vercel. Almost all of these: https://vercel.com/customers

@gwer
Copy link
Contributor Author

gwer commented Jan 19, 2022

@timneutkens

pre-requireing the _app and _document is probably fine for the standalone mode @ijjk. I think this PR is fine to land without experimental opt-in if it checks for minimalMode, especially now that the target option is deprecated we can make improvements like these.

Do you mean that we should check only minimalMode instead of new experimental option? Sounds good. But I still think that it's not enough to preimport only _app and _document. Pages import can take a long time too. And it would be useful to be able to enable pages preimport like here. This will provide an opportunity to improve performance when stable TTFB is more important than server startup speed.

I see two options:

  1. Pre-import _app and _document with !minimalMode and preimport pages with !minimalMode && nextConfig.experimental.pagesPreimport. It will make some speed-up for all by default.
  2. Pre-import _app, and _document, and pages with nextConfig.experimental.pagesPreimport. This solution will keep default behavior.

We can also make more complex and flexible configuration.

  • If someone has only static optimized pages or they want to startup server ASAP (in dev environment for example), they don't need pre-import at all.
  • If someone cares about TTFB, they need full pre-import.
  • If someone has many instances of server with route-based balancer or with multi zones, they may need to pre-import only _app and _documents. This configuration will slow down the first requests, but will help to avoid overhead. But if requests time is more important for you then some extra memory consumption and server startup speed you may still need to enable all pages pre-import even in this case. Until it becomes possible to pre-import only the necessary pages.

@abriginets
Copy link

@leerob is there a chance that those customers host their backends somewhere else instead of storing all the codebase in Next.js as monorepo? Maybe my issue with Next.js lies in extensive backend codebase leading Next.js to struggle with it at the runtime?

@ijjk
Copy link
Member

ijjk commented Feb 10, 2022

Stats from current PR

Default Build (Increase detected ⚠️)
General Overall increase ⚠️
vercel/next.js canary gwer/next.js app_document_pre_warm Change
buildDuration 19.7s 19.4s -286ms
buildDurationCached 7.4s 7.5s ⚠️ +33ms
nodeModulesSize 359 MB 359 MB ⚠️ +705 B
Page Load Tests Overall increase ✓
vercel/next.js canary gwer/next.js app_document_pre_warm Change
/ failed reqs 0 0
/ total time (seconds) 4.235 4.177 -0.06
/ avg req/sec 590.33 598.45 +8.12
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 2.173 2.116 -0.06
/error-in-render avg req/sec 1150.49 1181.39 +30.9
Client Bundles (main, webpack, commons)
vercel/next.js canary gwer/next.js app_document_pre_warm Change
450.HASH.js gzip 179 B 179 B
framework-HASH.js gzip 42 kB 42 kB
main-HASH.js gzip 27.9 kB 27.9 kB
webpack-HASH.js gzip 1.44 kB 1.44 kB
Overall change 71.5 kB 71.5 kB
Legacy Client Bundles (polyfills)
vercel/next.js canary gwer/next.js app_document_pre_warm Change
polyfills-HASH.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary gwer/next.js app_document_pre_warm Change
_app-HASH.js gzip 1.36 kB 1.36 kB
_error-HASH.js gzip 194 B 194 B
amp-HASH.js gzip 312 B 312 B
css-HASH.js gzip 326 B 326 B
dynamic-HASH.js gzip 2.57 kB 2.57 kB
head-HASH.js gzip 350 B 350 B
hooks-HASH.js gzip 919 B 919 B
image-HASH.js gzip 5.01 kB 5.01 kB
index-HASH.js gzip 263 B 263 B
link-HASH.js gzip 2.26 kB 2.26 kB
routerDirect..HASH.js gzip 321 B 321 B
script-HASH.js gzip 383 B 383 B
withRouter-HASH.js gzip 318 B 318 B
85e02e95b279..7e3.css gzip 107 B 107 B
Overall change 14.7 kB 14.7 kB
Client Build Manifests
vercel/next.js canary gwer/next.js app_document_pre_warm Change
_buildManifest.js gzip 459 B 459 B
Overall change 459 B 459 B
Rendered Page Sizes
vercel/next.js canary gwer/next.js app_document_pre_warm Change
index.html gzip 531 B 531 B
link.html gzip 544 B 544 B
withRouter.html gzip 526 B 526 B
Overall change 1.6 kB 1.6 kB

Default Build with SWC (Decrease detected ✓)
General Overall increase ⚠️
vercel/next.js canary gwer/next.js app_document_pre_warm Change
buildDuration 23.7s 23.9s ⚠️ +180ms
buildDurationCached 7.4s 7.6s ⚠️ +253ms
nodeModulesSize 359 MB 359 MB ⚠️ +705 B
Page Load Tests Overall decrease ⚠️
vercel/next.js canary gwer/next.js app_document_pre_warm Change
/ failed reqs 0 0
/ total time (seconds) 4.391 4.232 -0.16
/ avg req/sec 569.36 590.72 +21.36
/error-in-render failed reqs 0 0
/error-in-render total time (seconds) 2.079 2.172 ⚠️ +0.09
/error-in-render avg req/sec 1202.22 1151.24 ⚠️ -50.98
Client Bundles (main, webpack, commons)
vercel/next.js canary gwer/next.js app_document_pre_warm Change
450.HASH.js gzip 179 B 179 B
framework-HASH.js gzip 42.1 kB 42.1 kB
main-HASH.js gzip 27.9 kB 27.9 kB
webpack-HASH.js gzip 1.44 kB 1.44 kB
Overall change 71.6 kB 71.6 kB
Legacy Client Bundles (polyfills)
vercel/next.js canary gwer/next.js app_document_pre_warm Change
polyfills-HASH.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary gwer/next.js app_document_pre_warm Change
_app-HASH.js gzip 1.35 kB 1.35 kB
_error-HASH.js gzip 180 B 180 B
amp-HASH.js gzip 305 B 305 B
css-HASH.js gzip 321 B 321 B
dynamic-HASH.js gzip 2.56 kB 2.56 kB
head-HASH.js gzip 342 B 342 B
hooks-HASH.js gzip 911 B 911 B
image-HASH.js gzip 5.05 kB 5.05 kB
index-HASH.js gzip 256 B 256 B
link-HASH.js gzip 2.28 kB 2.28 kB
routerDirect..HASH.js gzip 314 B 314 B
script-HASH.js gzip 375 B 375 B
withRouter-HASH.js gzip 309 B 309 B
85e02e95b279..7e3.css gzip 107 B 107 B
Overall change 14.7 kB 14.7 kB
Client Build Manifests
vercel/next.js canary gwer/next.js app_document_pre_warm Change
_buildManifest.js gzip 458 B 458 B
Overall change 458 B 458 B
Rendered Page Sizes
vercel/next.js canary gwer/next.js app_document_pre_warm Change
index.html gzip 533 B 533 B
link.html gzip 547 B 547 B
withRouter.html gzip 527 B 527 B
Overall change 1.61 kB 1.61 kB
Commit: e503c9a

@ijjk ijjk merged commit 4634356 into vercel:canary Feb 10, 2022
AbhiPrasad pushed a commit to getsentry/sentry-javascript that referenced this pull request Feb 23, 2022
In our integration tests for nextjs, we use `nock` to intercept outgoing http requests, which lets us both examine a request’s payload and mock its response. As part of its initialization, `nock` monkeypatches  the `get` and `request` methods in both the `http` and `https` Node built-in modules. Our `Http` integration also monkeypatches those methods.

The order in which the two monkeypatching operations happen matters. If `nock` monkeypatches before Sentry does we end up with `sentryWrapper(nockWrapper(originalGet))`, which means that no matter what `nock` does or doesn’t do with `originalGet`, our wrapper code will always run. But if `nock` monkeypatches after we do, we end up with `nockWrapper(sentryWrapper(originalGet))`, meaning that if `nock` chooses not to call the function it’s wrapping, our code never runs.

Next 12.1 introduces a change in [this PR](vercel/next.js#23261), which causes Next to load the `_app` and `_document` pages as soon as the server starts, in the interest of serving the first requested page more quickly. This causes the order of the monkey patching to change, causing the http tests to fail for nextjs. 

This patch solves this by forcing `get` and `request` to be wrapped again by Sentry after they are wrapped by `nock`.

There are some TODOs that need to be addressed, but merging this patch to unblock CI.
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 12, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants