vercel / next.js

The React Framework
https://nextjs.org
MIT License
126.93k stars 26.98k forks source link

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

Closed rauchg closed 5 years ago

rauchg commented 6 years ago

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:

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 commented 6 years ago

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 commented 6 years ago

wou, this is awesome !! :o

Nishchit14 commented 6 years ago

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 commented 6 years ago

@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 commented 6 years ago

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.

LinusU commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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

Kikobeats commented 6 years ago

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

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

Enalmada commented 6 years ago

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 commented 6 years ago

@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 commented 6 years ago

@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 commented 6 years ago

@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 commented 6 years ago

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 commented 6 years ago

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

Enalmada commented 6 years ago

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 commented 6 years ago

@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 commented 6 years ago

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 commented 6 years ago

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

shauns commented 6 years ago

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?

southpolesteve commented 6 years ago

@shauns Unfortunately, I am no longer at Bustle and don't have the code to look at anymore :/

shauns commented 6 years ago

@southpolesteve no worries! Good to know its poss in webpack at least.

romainquellec commented 5 years ago

Can we have some news on next-server ? I saw some commits on this month ago.

timneutkens commented 5 years ago

Check the canary branch.

romainquellec commented 5 years ago

When do you plan to release it ?

timneutkens commented 5 years ago

I can't share a timeline at this point.

balupton commented 5 years ago

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

timneutkens commented 5 years ago

Just landed #5927

Skaronator commented 5 years ago

@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 commented 5 years ago

@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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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

gbreux commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

That's awesome! Looking forward to this!

Nickman87 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

@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 commented 5 years ago

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 commented 5 years ago

@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 commented 5 years ago

@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 commented 5 years ago

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 commented 5 years ago

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

azizhk commented 5 years ago

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
khilnani commented 5 years ago

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.