The problem: when optimizing for a production build, invoking next start
or using require('next')
within a custom server.js
involves bringing the entire set of next
dependencies, including the ones related exclusively to development, such as webpack
.
Not only is this problematic from a build image standpoint and download time performance when generating production builds, but it also likely hurts bootup time. _Note: This is lessened by the fact that we carefully lazily load heavy dependencies such as webpack
in dev mode._
For the performance conscious and those sensitive to _cold start times_ (see for example: https://twitter.com/rauchg/status/990667331205447680), we can introduce a next-server
package.
It would have the same capabilities as require('next')
minus all development-time settings, plus a very small next-server
CLI that can open a port and perform graceful shutdown.
What we want to optimize for:
next-server
has to be as small as possibleFurthermore, we should provide an example in examples/
of how to use next-server
in combination with pkg
to export your Next.js application as a self-contained ELF binary.
The cold start times we see on Now 2.0 for our frontend are 1.5s, for an image size of 80mb IIRC
It should be possible to make it a lot closer to 1s without any changes to Node or V8 or any of the dependencies whose cold evaluation take a good chunk of time (like react
and react-dom
)
wou, this is awesome !! :o
wow... that's great.
some questions for next-server
.
next-routes
?@Nishchit14 You wouldn鈥檛 add express
if you were trying to keep down build size.
I鈥檓 sure next-routes
will still work just fine.
So what we鈥檙e talking about here is extracting the existing server into it鈥檚 own package. So it鈥檒l work the same way as before, but instead of importing next, you import next-server.
This is awesome! I and other people that I know have been running Next.js on top of AWS Lambda using scandium (using this guide) and some of the main problems have been:
1) Package size. Lambda gives you a hard limit of 50MB which can be easy to get close to with all the dev-tools that are included.
2) Cold start. Having quick bootup is super important since Lambda can decide to spin up more servers at any point. Existing servers also live a maximum of ~4h so cold start will be important throughout the entire lifecycle of the application.
Very glad to see this initiative and happy to help out!
This is a great idea, we have the same with Nuxt.js, we called it nuxt-start
since it's the command you need to run nuxt start
-> nuxt-start
Following this closely. As a data point for what is possible, www.bustle.com is a SSR preact app on AWS Lambda with <1s cold starts. The entire deployed production zip file is 166kb. That is all the application and library code. Webpack is used for bundling.
Thanks for sharing @southpolesteve. That's super impressive. #goals
The user case looks very similar to micro
and micro-dev
.
Why not use the same nomenclature? next
and next-dev
I am messing around with next.js and serverless using this example and curious if there any way to accomplish smaller builds now. Is there a list of node_modules that we absolutely don't need in production and can be excluded with config files in a packager like serverless or repack-zip?
@Enalmada I'm running next.js with several deps and material-ui
being one of them, I have a pretty large app in terms of scale but the built zip I upload to Lambda is ~ 45MB. What sizes are you looking for?
@albinekb I am inspired by southpolesteve bustle.com response above of 166kb and wonder how much of my "45MB" is useless and easy to remove if I just knew what to put in a dist exclude file as a hack until this excellent ticket is finished.
@albinekb Highly recommend you look at using webpack, parcel, or rollup to bundle your JS for lambda. You'll save on size, but also boot time as hitting the filesystem via normal node require is pretty slow.
If you're deploying to ZEIT Now and you want to keep your image small for fast cold boots, you can use a tool like Package Phobia to check the size of a npm dependency before you install it (or just check the size of current dependencies to cut the bloat).
The readme also has many similar tools to help you fight bloat. Check it out here: https://github.com/styfle/packagephobia
Wasn't this supposed to be addressed in the Next 7 release? :(
If you're deploying to zeit now and you want to keep your image small for fast cold boots, you can use a tool like Package Phobia
Damn you antd: https://packagephobia.now.sh/result?p=antd
@Enalmada it's probably the dependencies of antd that are responsible, not the library itself. I have been looking into that for https://packagephobia.now.sh/result?p=%40material-ui%2Fcore. Most of the weight comes from one or two dependencies.
Wasn't this supposed to be addressed in the Next 7 release? :(
To be clear about this, Next.js 7 lays the foundation for Serverless Next.js, we've removed around 5 routes, leaving only 2 really needed for production.
Has anyone ever gotten next.js to work with rollup? I feel like I got very close...running rollup on my 60m dist file brought the size down to 6m. Unfortunately the dist file wouldn't actually startup and I think it is due to a single circular dependency in next.js code that is a warning during rollup. If someone could weigh in on the possibility of removing a circular dependency in next.js code we might all be very close to much smaller builds and faster bootup:
https://github.com/zeit/next.js/issues/5392
Following this closely. As a data point for what is possible, www.bustle.com is a SSR preact app on AWS Lambda with <1s cold starts. The entire deployed production zip file is 166kb. That is all the application and library code. Webpack is used for bundling.
@southpolesteve would you be able to share anything around your webpack bundle config?
@shauns Unfortunately, I am no longer at Bustle and don't have the code to look at anymore :/
@southpolesteve no worries! Good to know its poss in webpack at least.
Can we have some news on next-server ? I saw some commits on this month ago.
Check the canary branch.
When do you plan to release it ?
I can't share a timeline at this point.
Seems like this could help relieve this error:
https://spectrum.chat/zeit/general/unable-to-import-module-now-launcher-error~2662f0ba-4186-402f-b1db-2e3c43d8689a
Just landed #5927
@timneutkens Should I move next
to devDependencies and add next-server
to my dependencies or are you doing this automatically via babel?
https://github.com/zeit/next.js/blob/canary/packages/next/build/babel/plugins/next-to-next-server.ts
@Skaronator if you're implementing using #5927 it's neither, as per the specification it'll output one bundle per page, no dependencies needed. Meaning you can take .next/serverless/index.js
require it (require('./.next/serverless/index.js')
) and then call it's render
method:
const page = require('./.next/serverless/index.js')
page.render(req, res)
This will render the page and finish the response
That's awesome!
I'm trying this out but I have some trouble making it work on aws lambda. Does anyone have any tips?
I guess we shouldn't need a custom express server anymore, we could just require the serverless file based on the path 馃
edit
This seems to work, need to dig deeper though to see how to optimize the build step:
const serverless = require("serverless-http");
const http = require('http');
const app = require('./.next/serverless/index.js');
const server = new http.Server((req, res) => app.render(req, res))
app.prepare().then(() => {
const handler = serverless(server, {
binary: binaryMimeTypes
});
return handler(event, context, callback);
});
Looks like you're confusing the "custom server" with "serverless", their APIs are completely separate, there is no .prepare
method in serverless as there's nothing to prepare, we immediately render the page to html and finish the response when render
is called.
const serverless = require("serverless-http");
const http = require('http');
const page = require('./.next/serverless/index.js');
const server = new http.Server((req, res) => page.render(req, res))
const handler = serverless(server, {
binary: binaryMimeTypes
});
handler(event, context, callback);
Indeed, and i have no idea why the code above worked (maybe it was just the cache) but neither of mine or your piece of code worked because serverless-http
doesn't seem to support http.Server
and I can't just return page.render(req, res)
because the lambda event
object can't replace the necessary render req
param..
Also, i don't want to use express/koa/whatever since it will break the whole purpose of this next new feature.. (serverless-http
is dependencie free so it was ok to use)
I'm out of ideas :/
Maybe this will help you @guillaumebreux https://github.com/zeit/now-builders/blob/master/packages/now-node-bridge/bridge.js
Thanks @timneutkens, I appreciate your help.
But it's not working either at the moment, I still have this error: typeError: Parameter "url" must be a string, not undefined
I'll stop polluting this thread and keep digging and i'll write an example if I ever find a solution 馃槃
I'm a bit unclear. This thread looks like it applies to two scenarios: server-less apps and pre-compiled servers that include all of the necessary server-side npm packages webpack'd together.
The gif in the first comment on this thread appears to regard the latter scenario, which is the one I'm interested in. It looks like it uses next-server
which may or may not be this npm package--it doesn't have a repository attached to it, and I couldn't find one via google or GitHub search, though one of the version tags is 8.0.0-canary.7--a version tag of next--so I suspect it is the right package.
Is what I've written so far accurate? If so, even though it's in canary, is there any way I can get early access to it?
My current solution (which for evident reasons, I'm not using in prod) is to remove the function from config.externals
in my next.config.js
. This *appears to package all the node modules properly, but, for reasons I don't understand, it causes styles to be loaded late on the client side, resulting in an unstyled page for half a second on every page load. (My hunch is that it's because I have a theme context that loads different style sheets based on the selected theme, and the server is isn't code splitting very well to make a common vendors bundle.)
I would love to be able to produce a pre-built server so that I don't need to install 200MB of node_modules and then spend 2 minutes compiling on my poor, little production VM every time I push an update.
* "prod" is used loosely, as this project isn't mission critical or all that professional
Following this closely. As a data point for what is possible, www.bustle.com is a SSR preact app on AWS Lambda with <1s cold starts. The entire deployed production zip file is 166kb. That is all the application and library code. Webpack is used for bundling.
Next.js 8 serverless target has a zip size of 42Kb by default 馃槍
That's awesome! Looking forward to this!
I have exactly the same question as @dfoverdx. I want to make a server build, that also includes all the node_modules needed for running. I am using a custom server with express so I don't expect those dependencies to be included in the package, but now you have to install _all_ dependencies on your server too (react, next, axios, ...).
I don't understand how this is not by default?
Packaging all dependencies and being able to minimize them should bring significant serverside performance improvements or am I completely wrong here?
Overwriting the externals
section of the webpack config as follows includes most dependencies
module.exports = {
webpack: (config, { dev }) => {
config.externals = [];
return config;
})
};
But react and react-dom are still required on the server. I cannot figure out how to include those as well...
Unfortunately it's not possible to create custom server with current serverless mode. And if you use normal mode you need to include next and all its dependencies because generated _app.js in .next is depending for example on next/router
Why coudn't non-serverless mode also bundle next?
Unfortunately it's not possible to create custom server with current serverless mode. And if you use normal mode you need to include next and all its dependencies because generated _app.js in .next is depending for example on next/router
Note, that since next 8, you can require 'next-server' instead of 'next' in your server.js, and you only losing hot reloading during local development by doing that. In theory it gives you ability to do CI build on intermediate server and not to copy Webpack related dependencies to production instances. But we haven't yet tried it in our project.
@ElvenMonky waiting for something like this since a year, but could not find anything about this in the docs or examples.
@timneutkens could you please verify this?
If so, I might experiment with such a setup, and send a PR for the docs/examples.
Note, that since next 8, you can require 'next-server' instead of 'next' in your server.js, and you only losing hot reloading during local development by doing that
Unfortunately this doesn't work.
First, running serverless build with server target is actively blocked with following message: "Cannot start server when target is not server. https://err.sh/zeit/next.js/next-start-serverless"
Then, if you decide do to do normal build, then build files are referencing things from next
package directly (like next/router
in compiled _app.js file for server-side). It means next
and webpack
stuff needs to be in production build anyway.
@ElvenMonky
Note, that since next 8, you can require 'next-server' instead of 'next' in your server.js, and you only losing hot reloading during local development by doing that.
Next is doing that internally as a Babel Plugin as you can see here:
https://github.com/zeit/next.js/blob/709850154754278d2fc86b987eebe1b3f0565255/packages/next/build/babel/plugins/commonjs.ts#L5-L32
@sheerun as I mentioned also in #7011 you can eliminate unresolved next/router
dependency by transpiling next
module using next-transpile-modules
plugin.
I've forked and adjusted example for custom express server to illustrate the solution: https://github.com/ElvenMonky/next.js/tree/custom-next-server-express/examples/custom-server-express
P.S.: I'm still really excited about #5927 no matter, that my application requires everything listed in TODO, not to mention dynamic routes and serving static content.
Good news is that above solution seems to play well with https://www.npmjs.com/package/next-serverless custom server setup, making it possible to deploy next into e.g. AWS Lambda without aforementioned limitations.
Note, that since next 8, you can require 'next-server' instead of 'next' in your server.js, and you only losing hot reloading during local development by doing that.
I've been using this advice, but unfortunately can't use Runtime Configuration as that requires next/config
which requires next
.
I don't know why but require('next/config')
used to work in production without next
installed in node_modules with next version 8.0.3 but does not work with in next version 8.1.0
Is it possible to move next/config to a different package, like next-runtime-config
?
Or next-runtime-vars
(avoiding the term config to avoid confusion with next.config.js).
Let me know if acceptable, I'll create a PR.
Hey everyone! This issue has been implemented since Next.js 8, and is still around in Next.js 9. I'm going to close this as completed. 馃槍
Sorry I got confused with this issue: https://github.com/zeit/next.js/issues/7011
I haven't checked with target: "serverless"
See deleted comment
Some "next/*"
can be replaced with "next-server/*"
, like:
But there are a few for which there is no support for such optimization mentioned by OP of this issue.
Not needed as they are inlined even in server (AKAIK)
Hey everyone! This issue has been implemented since Next.js 8, and is still around in Next.js 9. I'm going to close this as completed.
Hi @Timer - Are you referring to the servless target, or the replacing of 'next ' with 'next-server' ?
If we replace next with next server, that doesnt change the node_modules folder size unless the packages.json dependencies is also updated.
Is there an example of either approach available? with serverless, my use case is deploying to AWS Lambda.
The approach outlined in the initial issue evolved into the serverless
target, we recommend you use that.
@timneutkens serverless
target doesn't and can not solve problems with custom server, which uses dynamic routes. #5927 Is not a solution for a lot of real world business applications like in my case, where we have to use dynamically generated pages, assets prefix, custom _app, _document and _err: basically everything stated in TODO list.
next-server
gives us partial solution to deploy to production without weird development only dependencies, like webpack and babel. This can be done however with some hacks and woodoo dancing, that we are discussing here.
I was under impression that you understand this difference and was hoping to see more robust solution someday to the initial issue as it is described by @rauchg
Most helpful comment
The cold start times we see on Now 2.0 for our frontend are 1.5s, for an image size of 80mb IIRC
It should be possible to make it a lot closer to 1s without any changes to Node or V8 or any of the dependencies whose cold evaluation take a good chunk of time (like
react
andreact-dom
)