Closed iam4x closed 4 years ago
You shouldn't override the serverless loader, instead write a utility that wraps the build output, the point of the serverless target, as documented is provide the low level rendering API so that it can be wrapped for other platforms. Note that the Next.js serverless build isn't particular to Now 2.0, Now 2.0 wraps the next build
output using a @now/next
builder. We might introduce a utility / api for handling this wrapping in the future though (just to make things easier). It's perfectly possible to generate AWS lambdas using the current API.
Eg like the example I posted here: https://github.com/zeit/next.js/pull/6070#issuecomment-454846201
Thank you for the fast answer.
Alright, I understand the API design but having to create a file with a wrapper function for every pages is redundant or having one file as handler for everything is going against the way of using lambdas.
On my side, it's a big +1 for an utility / api for handling this wrapping with Next.js directly and I'll be happy to help by working on that. But like you said, I'm not sure this is the scope of Next.js for now.
In the meantime I'll stick to my solution as part of my build phase in less hackish way.
βοΈ
As a starter point, you can push your PR.
As a reference point for others, our AWS Lambda deployment via serverless
tool (serverless deploy
) uses the following lambda handler:
const express = require("express");
const serverless = require("serverless-http");
const isProduction = process.env.NODE_ENV === "production";
// setup Express and hook up Next.js handler
const app = express();
const getPage = page => require(`./.next/serverless/pages/${page}`).render;
app.get("/", getPage("index"));
app.get("/pageA", getPage("pageA"));
app.get("/pageB", getPage("pageB"));
// for local development (serverless offline)
if (!isProduction) {
// host the static files
app.use("/_next/static", express.static("./.next/static"));
app.use("/static", express.static("./static"));
}
// 404 handler
app.get("*", require("./.next/serverless/pages/_error").render);
// export the wrapped handler for the Lambda runtime
exports.handler = serverless(app);
We then copy the static files to S3 (using assetPrefix
in our next.config.js
) and explicitly exclude the static directory from the bundle. In fact, we can also mark all dependencies apart from express
and serverless-http
as development dependencies (which will be excluded automatically from the Lambda zip), as the page runtimes in .next/serverless
files are fully self-contained.
I'm aware that the page runtimes can also be deployed on a per route basis as separate functions, but we're a bit concerned to do that right now due to us having had bad experiences with Lambda coldstarts in the past.
Nonetheless it would be great if someone built an automagic tool specifically for AWS Lambda. That could be a tool that automatically loads the page runtimes from the filesystem (like the script above), or automatically configures serverless to add appropriate routes to each page runtime wrapped up with an HTTP server each. I'm not sure whether lambda
really should be an explicit target for Next.js.
I'm aware that the page runtimes can also be deployed on a per route basis as separate functions, but we're a bit concerned to do that right now due to us having had bad experiences with Lambda coldstarts in the past.
The more data you have in one lambda zip the slower cold start gets though.
Good point. A single "heavy" Lambda is kept warm more easily though (e.g. through Cloudwatch Events from a Schedule Expressions). But I do see the issue if traffic scales up and we need more Lambdas around than we keep warm.
Edit: But I do get that, if cold starts are not an issue because the Lambda bootstraps within tens to hundreds of milliseconds, keeping warm is not a priority at all.
@beheh thanks for the example above! how would you build dynamic routes in this case? tried the following but does not work:
const pages = ls('pages')
pages.forEach(page => {
const pageName = path.parse(page).name
app.get(`/${pageName}`, require(`./.next/serverless/pages/${pageName}`).render)
})
thanks!
UPD: and here is the answer, with subfolders support:
app.get('/', require('./.next/serverless/pages/index').render)
app.get('*', (req, res) => {
const { pathname } = parse(req.url, true)
try {
require(`./.next/serverless/pages${pathname}`).render(req, res)
} catch (err) {
require('./.next/serverless/pages/_error').render(req, res)
}
})
Hi π I've been working on a plugin (https://github.com/danielcondemarin/serverless-nextjs-plugin) for the serverless framework that targets nextjs 8 serverless mode. It adds the compat layer between the AWS Lambdas and the Nextjs page handlers at build time and takes care of uploading the static assets to S3. Seems promising so far, but is still a WIP and haven't been able to test it in anger. Is this something the folks in this thread using the serverless framework would be interested at? Would be good to get some feedback.
Hi @danielcondemarin ! There is definitely a niche for plugin like this!
I can give it a try over the coming days or weekend and provide a more meaningful feedback, but two things come to my mind as I looked through it:
WDYT?
@krajowski Thanks for your feedback!
The dynamically built routes is something that should not be too difficult to add. This could be an opt-in feature in the plugin to let it create the serverless functions for you at build time based on the pages under .next/serverless/pages/*
. A feature proposal on the repo would be great, then we can polish the details!
The runtime env. vars not sure how would that work, AFAIK nextjs doesn't provide any out of the box way to do this, and people use dotenv
instead? Also, in the serverless.yml
env. support is already quite powerful.
The runtime env. vars not sure how would that work, AFAIK nextjs doesn't provide any out of the box way to do this, and people use
dotenv
instead? Also, in theserverless.yml
env. support is already quite powerful.
You'll want to use build time env
in next.config.js
instead. Changes to the environment should be considered reason to rebuild.
The runtime env. vars not sure how would that work, AFAIK nextjs doesn't provide any out of the box way to do this, and people use
dotenv
instead? Also, in theserverless.yml
env. support is already quite powerful.You'll want to use build time
env
innext.config.js
instead. Changes to the environment should be considered reason to rebuild.
Neat, I wasn't aware of this option, found it documented here. That's great because it should mean that the serverless page bundles get the env. bundled in. so there is really nothing to do for the plugin I'm working on. Will test this later π
Exactly!
@krajowski I started looking into dynamically deploying the pages via the serverless plugin, tracking the progress here
@danielcondemarin could you send me a dm on twitter.com/timneutkens / spectrum.chat/users/timneutkens
@beheh Thanks for sharing your code example! I'm trying to adopt your approach and wonder how do you not need binding _next/static
on production as well. I understand from Next.js 8 documentation that the serverless js pages should be standalone but they still try to XHR all the static content in my case. Am I missing something in my config? Here's my index.js
and serverless.yml
, my application is dead simple with just one page.
const sls = require('serverless-http');
const binaryMimeTypes = require('./binaryMimeTypes');
const express = require('express');
const app = express();
const getPage = page => require(`./.next/serverless/pages/${page}`).render;
app.get("/", getPage("index"));
// Doesn't work when deployed without these 2 lines
app.use("/_next/static", express.static("./.next/static"));
app.use("/static", express.static("./static"));
// 404 handler
app.get("*", require("./.next/serverless/pages/_error").render);
module.exports.server = sls(app, {
binary: binaryMimeTypes
});
service: ternovka-frontend
provider:
name: aws
runtime: nodejs8.10
stage: ${self:custom.secrets.NODE_ENV}
region: us-east-1
environment: ${file(secrets.json)}
functions:
server:
handler: index.server
events:
- http: ANY /
- http: ANY /{proxy+}
plugins:
- serverless-apigw-binary
- serverless-offline
package:
exclude:
- .venv/**
- .idea/**
- .git/**
- node_modules/**/.cache/**
- '**.test.js'
- .ebextensions/**
- .elasticbeanstalk/**
- .vscode/**
- .secrets/**
- secrets.json
- secrets.dev.json
- /*.key
- /*.pem
- /*.cer
custom:
secrets: ${file(secrets.json)}
apigwBinary:
types:
- '*/*'
@bausk Take a look at https://github.com/zeit/next.js/#cdn-support-with-asset-prefix! We intentionally exclude them from the Lambda zip and push the static assets to S3 instead.
Excellent, thanks a lot!
I've been working with Next on AWS Lambda for more than a year, and the lack of official example/support is painful indeed.
It's really not easy to properly setup next on AWS, and it's even harder to have a local/dev env that properly simulates AWS Lambda.
Also, AWS Lambda is -by far- the most used (compared to GCP, Azure, and others), and it would really make sense for the Next.js community to target this system, as it is one of the cheapest yet most reliable out there. Also, it fits particularly well with the Next 8+ "serverless" approach, but needs guidance to do it right.
The most advanced way I've seen so far is https://github.com/danielcondemarin/serverless-nextjs-plugin. I had made my own AWS Lambda + Next 5 + Express back then https://github.com/Vadorequest/serverless-with-next and I'm still running this in production (and looking for an up-to-date replacement, having a hard time to upgrade myself, see https://github.com/zeit/next.js/issues/7823)
There have been numerous open source attempts to make AWS Lambda work with Next over the past year, I can't count how many work-in-progress repositories I've come across, such as https://github.com/justinwhall/nextjs-serverless-aws
The recent Next 9 update brings a lot of much welcomed capabilities/possibilities that are currently in discussion, see https://github.com/danielcondemarin/serverless-nextjs-plugin/issues/101 and https://github.com/danielcondemarin/serverless-nextjs-plugin/issues/105.
So far, I've seen two very different ways to go "serverless" with Next:
Honestly, I don't know what's the best approach here. Both have their pros/cons. I like the express way, because it makes it so much easier to use middlewares and it's a huge time saver. I haven't seen anything similar to middlewares using the "serverless" way so far. It's much heavier, but also easier to warmup. And I don't know how much faster does the "serverless" way is for cold starts. Are they really so insignificant that we can ignore them? Not quite sure about that. What I know is that if we need to warmup in "serverless" mode, it'll be much more difficult/expensive to do so.
Also, I don't know how many existing apps are out there using the "express" way, and it's gonna be hard to migrate apps from "express" to "serverless", as we must find a proper replacement for all middlewares, and other things.
So, the point of all this talking is to clarify that there are -at least- 2 ways to use Next.js on AWS, and it hasn't been proved yet that one way is "better" than the other. Also, there is a lot of efforts made by the community to make Next and AWS Lambda play together, but very few successful ones due to the complexity around those.
It'd very much be a game changer if the Next.js core team would tackle this issue and provide a reliable way of making Next working on AWS Lambda.
I've been working with Next on AWS Lambda for more than a year, and the lack of official example/support is painful indeed.
As far as I know the official way of deploying Next.js is via Now, Zeit's cloud platform. Their platform is serverless as of Now 2.0 and can deploy Next.js and other platforms to both AWS and GCP. The tooling used there is the "next" now-builder (docs) which already generates all the final lambdas and asset artifacts. Presumably there's some production-grade tooling to manage all of that in the respective cloud (AWS/GCP), including setting up S3 buckets and API Gateway for AWS- but that probably belongs to the more proprietary parts of the Now platform (and I don't think that part is open source).
With that in mind it seems unlikely that we'll see an official tool to directly push the next build
output to AWS where it completely bypasses the Now platform, outside of maybe a community contribution. I don't think that's unreasonable at all, but we probably shouldn't hold our breath waiting for an official tool but should rather get to building and contributing one as members of the community.
You can probably take that now-builder above and write a script to orchestrate the final deployment around it. I'm guessing most of the work is getting the full lifecycle sorted out, and based on the experience at my company the API Gateway has always been very painful to work with, even through tooling like serverless as soon as you want to do something like writing a ton of custom routes.
Updated this reply as it got mis-interpreted.
You can read more about deployment here: https://nextjs.org/docs/deployment
There's a bunch of community plugins to handle serverless deployments with AWS: https://github.com/zeit/next.js/issues/6173#issuecomment-466825144
The serverless target gives a deployment target independent solution so that it can be used to create solutions for every possible hosting provider. Not just ZEIT, AWS or Google Cloud functions.
You can write a solution on top of the serverless target like the community plugins.
@timneutkens That's a disappointing reply. Totally understand recommending your product for many use cases, but dismissing self-hosting on other serverless platforms because of proprietary optimizations ignores many valid reasons for self-hosting - internal sites on a private network or behind a VPN, sites deployed as part of infra-as-code, etc. I'd love to see Zeit reconsider this position.
A community plugin exists for hosting using serverless framework: https://github.com/zeit/next.js/issues/6173#issuecomment-466825144
The serverless target allows you to host anywhere you want and isn't limited to AWS Lambda as it accepts Node.js req
and res
.
However a Next.js app since Next.js 9 is no longer "just a server" as said in my earlier reply. You can't get away with only using AWS Lambda, you also have to serve static pages. When https://github.com/zeit/next.js/issues/9524 lands it'll become even more complicated as you'll have to know how to route a page to a static file and lambdas for the same route.
Deployment to self-hosted container or server solutions is just next build
and then next start
: https://nextjs.org/docs/deployment#self-hosting
BTW, I make a small webpack plugin https://github.com/vincent-herlemont/next-aws-lambda-webpack-plugin for get around this issue and allow us to use Next.JS with native AWS solution deployment like SAM or Cloudfomation.
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.
Hey π
Thank you a lot @timneutkens for great #5927 PR, amazing work π₯
I was going through the code of your PR in order to make it work with AWS Lambda and it was pretty straightforward.
I had to "hack" your
next-serverless-loader.ts
function in order to be able to injectserverless-http
module that way:This is working great, the output can be directly plugged to an handler π
Here are my questions:
serverless-http
module?Cheers βοΈ