next.js: Slow initial page render
What version of Next.js are you using?
10.0.9
What version of Node.js are you using?
v15.11.0
What browser are you using?
Chrome
What operating system are you using?
macOS
How are you deploying your application?
next start
Describe the Bug
First render of each page has large TTFB. It can be seen in any example (e.g. api-routes-apollo-server-and-client-auth-app).
$ yarn create next-app --example api-routes-apollo-server-and-client-auth
$ yarn build
$ NODE_ENV=production yarn start
First request of localhost:3000/about has TTFB about 1–1.5s. All subsequent requests will have TTFB about 20ms. This is also reproducible if using a simple custom server.
This overhead is due to the fact that dependencies are imported on demand when the user requests a page (https://github.com/vercel/next.js/blob/27555c8ef9b06ff71e245e086db191103ecd2026/packages/next/next-server/server/next-server.ts#L1276-L1280).
In real complex applications this overhead can increase TTFB to tens of seconds.
The second time the page loads faster, since the modules are cached (in ssr-module-cache.js).
Module import is reading the file and interpreting the code. These are two very slow operations, especially if there is a lot of code.
Let’s imagine an application with two pages: page1 and page2. It will compiled into the next 4 modules: _app, _document, page1, page2. Using this application can be summarized as the following table.
| Cache | Action | Missing dependencies | Speed |
|---|---|---|---|
| [] | Server start | [] | fast |
| [] | Request page1 | [_app, _document, page1] |
slow |
[_app, _document, page1] |
Request page1 | [] | fast |
[_app, _document, page1] |
Request page2 | [page2] |
medium |
[_app, _document, page1, page2] |
Request page2 | [] | fast |
You can see freeze even if page is statically optimized (because _app and _document are imported even in the case of static optimization).
I think some issues and duscussions about performance (e.g. https://github.com/vercel/next.js/discussions/12447) is about this overhead.
But! I have a workaround. It looks like warming up the cache.
const path = require("path");
const serverPath = path.join(__dirname, "./.next/server");
module.exports = () => {
try {
const pagesManifest = require(path.join(serverPath, "pages-manifest.json"));
Object.values(pagesManifest).forEach((dep) => {
if (path.extname(dep) !== ".js") {
return;
}
console.log("preimport ", dep);
require(path.join(serverPath, dep));
});
} catch (e) {}
};
Full example:
- https://github.com/gwer/next-js-initial-freeze-workaround
- https://github.com/gwer/next-js-initial-freeze-workaround/blob/master/pages-preimport.js
If you run this function before starting the custom server, it will save you from overhead.
| Cache | Action | Missing dependencies | Speed |
|---|---|---|---|
| [] | Server start | [_app, _document, page1, page2] |
slow |
[_app, _document, page1, page2] |
Request page1 | [] | fast |
[_app, _document, page1, page2] |
Request page1 | [] | fast |
[_app, _document, page1, page2] |
Request page2 | [] | fast |
[_app, _document, page1, page2] |
Request page2 | [] | fast |
This approach has two minor drawbacks:
- Server starts slower.
- Memory consumption grows faster. But the peak memory consumption is the same as without using the workaround.
How about doing the same, but inside the Next.js server?
Expected Behavior
There should be no overhead for the first page rendering.
To Reproduce
$ yarn create next-app --example api-routes-apollo-server-and-client-auth
$ yarn build
$ NODE_ENV=production yarn start
Open localhost:3000/about twice and look at TTFB.
Current state (2022-09)
_app and _document are preimporting by default since February 2022 (https://github.com/vercel/next.js/pull/23261).
For pages preimport you can use simple package next-pages-preimport.
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Reactions: 48
- Comments: 23 (15 by maintainers)
Any news about this issue ? I am deploying a dummy app with Vercel and the app launches in 11 seconds which timeout for a free version.
I went with a paid version but 11sec to launch a page is not acceptable.
As I am using Vercel, the workaround with custom server does not apply so I would really hope for a solution from the NextJS team.
Please merge the above mentioned PR 🙏
Very similarly to @chrskrchr my team saw the same thing - a whole order of magnitude reduction in TTFB when we implemented the workaround in our server for the initial page load after server spin-up. I would add my voice here in requesting for https://github.com/vercel/next.js/pull/23261 to be merged.
Hi, since this issue is pretty stale and generic, I’ll be closing this. With regard to start-up performance, we’re considering changing Next 15 to always warm up all the code on boot. You can try this out with
config.experimental.preloadEntriesOnStarton the latest canary.My team was facing a similar issue w/ long TTFB on the first page request after startup and we came across this thread. We can confirm that after hacking the experimental code from #23261 into our local Next.js server, that we see a significant drop in first page load times after startup - from ~
2sdown to0.1s.@ijjk - could the Next.js team consider merging #23261?
I’ve resorted to using @gwer 's workaround, as our app has very little traffic, only 2 pages, and scales down to 0. Dependecy import upon request means additional latency on top of cold start, which isn’t great.
Would love an option to handle pre-import without having to use a custom server ! Or better yet a to control what gets “lazy loaded” or not.
In the meantime, thanks @gwer !
I have now added experimental.prewarmRequiredPages for _app and _document prewarming in # 23261.
But I think that if we want to have different options for
[_document, _app]and for[_document, _app, ... pages]in the future, then only for _app and _document we need another option name.I also want to point out that we have been using a prewarming workaround for all pages in our projects for four months now. And it works well for us.
@jgabriele it looks like this is from importing directly from
@mui/icons-materialwhich requires all 1900+ icon components which is very slow, the below require is done on an M1 Pro so could be even slower on other systems. If you import the exact icon directly instead e.g.import Add from '@mui/icons-material/Add'you should see much better performance