Next.js: Serverless Next: make `next` dev-only dependency, introduce `next-server` for smaller builds and faster bootup

Created on 29 May 2018  路  52Comments  路  Source: vercel/next.js

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:

  • The total dependency set of next-server has to be as small as possible
  • We must heavily optimize for bootup time to start as quickly as possible

Furthermore, 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.

p1 feature request

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

2018-05-29 16 50 37

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)

All 52 comments

The cold start times we see on Now 2.0 for our frontend are 1.5s, for an image size of 80mb IIRC

2018-05-29 16 50 37

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.

  1. will it be a light express server?
  2. yes, will it be configurable with express routes and 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.

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 :/

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:

  • next/config -> next-server/config
  • next/amp -> next-server/amp
  • next/dynamic -> next-server/dynamic
  • next/constants -> next-server/constants
  • next/head -> next-server/head

But there are a few for which there is no support for such optimization mentioned by OP of this issue.

  • next/router -> next-server/dist/lib/router/router (maybe)? (Should be empty or should throw error if used on server)
  • next/link ?

Not needed as they are inlined even in server (AKAIK)

  • next/app
  • next/document

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

formula349 picture formula349  路  3Comments

irrigator picture irrigator  路  3Comments

havefive picture havefive  路  3Comments

ghost picture ghost  路  3Comments

sospedra picture sospedra  路  3Comments