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

Serverless Next: make next dev-only dependency, introduce next-server for smaller builds and faster bootup #4496

Closed
rauchg opened this issue May 29, 2018 · 53 comments
Assignees

Comments

@rauchg
Copy link
Member

rauchg commented May 29, 2018

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.

@rauchg
Copy link
Member Author

rauchg commented May 29, 2018

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)

@markozxuu
Copy link
Contributor

wou, this is awesome !! :o

@Nishchit14
Copy link

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?

@tim-phillips
Copy link
Contributor

@Nishchit14 You wouldn’t add express if you were trying to keep down build size.

I’m sure next-routes will still work just fine.

@timneutkens
Copy link
Member

So what we’re talking about here is extracting the existing server into it’s own package. So it’ll work the same way as before, but instead of importing next, you import next-server.

@timneutkens timneutkens self-assigned this May 30, 2018
@LinusU
Copy link

LinusU commented May 30, 2018

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!

@Atinux
Copy link

Atinux commented May 30, 2018

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

@southpolesteve
Copy link

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.

@rauchg
Copy link
Member Author

rauchg commented May 30, 2018

Thanks for sharing @southpolesteve. That's super impressive. #goals

@Kikobeats
Copy link
Member

The user case looks very similar to micro and micro-dev.

Why not use the same nomenclature? next and next-dev

@Enalmada
Copy link
Contributor

Enalmada commented Sep 5, 2018

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?

@albinekb
Copy link
Contributor

albinekb commented Sep 5, 2018

@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?

@Enalmada
Copy link
Contributor

Enalmada commented Sep 5, 2018

@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.

@southpolesteve
Copy link

@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.

@styfle
Copy link
Member

styfle commented Sep 6, 2018

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

@dihmeetree
Copy link

Wasn't this supposed to be addressed in the Next 7 release? :(

@Enalmada
Copy link
Contributor

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

@oliviertassinari
Copy link
Contributor

oliviertassinari commented Sep 21, 2018

@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.

@timneutkens
Copy link
Member

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.

@Enalmada
Copy link
Contributor

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:
#5392

@shauns
Copy link

shauns commented Nov 6, 2018

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?

@Skaronator
Copy link
Contributor

@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

@timneutkens
Copy link
Member

timneutkens commented Jan 2, 2019

@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

@gbreux
Copy link

gbreux commented Jan 9, 2019

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);
});

@timneutkens
Copy link
Member

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);

@gbreux
Copy link

gbreux commented Jan 10, 2019

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

@timneutkens
Copy link
Member

Maybe this will help you @guillaumebreux https://github.com/zeit/now-builders/blob/master/packages/now-node-bridge/bridge.js

@gbreux
Copy link

gbreux commented Jan 11, 2019

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 😄

@dfoverdx
Copy link

dfoverdx commented Jan 15, 2019

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

@timneutkens
Copy link
Member

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 😌

@tonyxiao
Copy link

That's awesome! Looking forward to this!

@Nickman87
Copy link

Nickman87 commented Mar 14, 2019

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

@sheerun
Copy link
Contributor

sheerun commented Apr 11, 2019

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?

@ElvenMonky
Copy link

ElvenMonky commented Apr 12, 2019

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.

@necccc
Copy link

necccc commented Apr 12, 2019

@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.

@sheerun
Copy link
Contributor

sheerun commented Apr 12, 2019

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.

@Skaronator
Copy link
Contributor

Skaronator commented Apr 13, 2019

@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

@ElvenMonky
Copy link

ElvenMonky commented Apr 14, 2019

@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.

@azizhk
Copy link
Contributor

azizhk commented May 9, 2019

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.

@Timer
Copy link
Member

Timer commented Jul 11, 2019

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. 😌

@Timer Timer closed this as completed Jul 11, 2019
@azizhk
Copy link
Contributor

azizhk commented Jul 16, 2019

Sorry I got confused with this issue: #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

@khilnani
Copy link

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.

@timneutkens
Copy link
Member

The approach outlined in the initial issue evolved into the serverless target, we recommend you use that.

@ElvenMonky
Copy link

@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

@balazsorban44
Copy link
Member

This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@vercel vercel locked as resolved and limited conversation to collaborators Jan 31, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests