@xiphe/serverless-nextjs-plugin

A serverless plugin for nextjs 8 serverless target

Usage no npm install needed!

<script type="module">
  import xipheServerlessNextjsPlugin from 'https://cdn.skypack.dev/@xiphe/serverless-nextjs-plugin';
</script>

README

Serverless Nextjs Plugin

serverless Build Status npm version Coverage Status Codacy Badge

A serverless framework plugin to deploy nextjs apps.

The plugin targets Next 8 serverless mode

demo

Contents

Motivation

Next 8 released official support for serverless! It doesn't work out of the box with AWS Lambdas, instead, next provides a low level API which this plugin uses to deploy the serverless pages.

Nextjs serverless page handler signature:

exports.render = function(req, res) => {...}

AWS Lambda handler:

exports.handler = function(event, context, callback) {...}

A compat layer between the nextjs page bundles and AWS Lambda is added at build time:

const page = require(".next/serverless/pages/somePage.js");

module.exports.render = (event, context, callback) => {
  const { req, res } = compatLayer(event, callback);
  page.render(req, res);
};

Getting started

Installing

npm install --save-dev serverless-nextjs-plugin

Out of the box, the plugin won't require any configuration. If you need to override any defaults check this.

For example:

nextApp
│   next.config.js
│   serverless.yml
└───pages
│   │   home.js
│   │   about.js
│   │   admin.js

Edit the serverless.yml and add:

plugins:
  - serverless-nextjs-plugin

package:
  exclude:
    - ./**

You can exclude everything. The plugin makes sure the page handlers are included in the artifacts.

Hosting static assets

If you don't want to manage uploading the next static assets yourself, like uploading them to a CDN, the plugin can do this for you by hosting the asset files on S3.

The easiest way is to use a valid bucket URL in the assetPrefix field of your next configuration:

// next.config.js
module.exports = {
  assetPrefix: "https://s3.amazonaws.com/your-bucket-name"
};

The plugin will create a new S3 Bucket using the parsed name. On deployment, static assets will be uploaded to the bucket provisioned.

Alternatively, if you just want the assets to get uploaded to S3, you can provide the bucket name via the plugin config:

# serverless.yml
plugins:
  - serverless-nextjs-plugin

custom:
  serverless-nextjs:
    assetsBucketName: "your-bucket-name"

With this approach you could have a CloudFront distribution in front of the bucket and use a custom domain in the assetPrefix.

If you need the static assets available in the main domain of your application, you can use the routes configuration to proxy API Gateway requests to S3. For example, to host /robots.txt:

custom:
  serverless-nextjs:
    staticDir: ./assets
    routes:
      - src: ./assets/robots.txt
        path: robots.txt

Note that for this to work, an S3 bucket needs to be provisioned by using the assetsBucketName plugin config or assetPrefix in next.config.js.

Deploying

serverless deploy

When running serverless deploy all your next pages will be automatically compiled, packaged and deployed.

The Lambda functions created for each page have by default the following configuration:

handler: /path/to/page/handler.render
events:
  - http:
      path: pageName # home, about, etc. Unless is the index page which is served at /
      method: get
  - http:
      path: pageName # home, about, etc. Unless is the index page which is served at /
      method: head

Deploying a single page

If you need to deploy just one of your pages, simply run:

serverless deploy function --function pageFunctionName

where pageFunctionName will be the page file name + "Page". For example, to deploy pages/home.js, you can run:

serverless deploy function --function homePage

Overriding page configuration

You may want to have a different configuration for one or more of your page functions. This is possible by setting the pageConfig key in the plugin config:

plugins:
  - serverless-nextjs-plugin

custom:
  serverless-nextjs:
    pageConfig:
      about:
        memorySize: 512 # default is 1024
      home:
        timeout: 10 # default is 6

If you need to change the default configuration, such as memorySize, timeout etc. use the top level provider which will override all the functions configuration. For example, to change the memorySize to 512MB:

provider:
  name: aws
  runtime: nodejs8.10
  memorySize: 512
  ...

You can also add configuration for all page functions by adding an asterisk entry (*) to pageConfig. This is particularly useful when you have other functions in your service (i.e. an api) aside from the page functions and you only want to apply configuration changes to the latter:

plugins:
  - serverless-nextjs-plugin

custom:
  serverless-nextjs:
    pageConfig:
      "*":
        layers:
          - arn:aws:lambda:${self:provider.region}:553035198032:layer:nodejs12:1

You can set any function property described here.

Custom page routing

The default page routes follow the same convention as next useFileSystemPublicRoutes documented here.

E.g.

page path
pages/index.js /
pages/post.js /post
pages/blog/index.js /blog
pages/categories/uno/dos.js /categories/uno/dos

You may want to serve your page from a different path. This is possible by setting your own http path in the routes config. For example for pages/post.js:

class Post extends React.Component {
  static async getInitialProps({ query }) {
    return {
      slug: query.slug
    };
  }
  render() {
    return <h1>Post page: {this.props.slug}</h1>;
  }
}

export default Post;
plugins:
  - serverless-nextjs-plugin

custom:
  serverless-nextjs:
    routes:
      - src: post
        path: posts/{slug}
        request:
          parameters:
            paths:
              slug: true

Custom error page

404 or 500 errors are handled both client and server side by a default component error.js, same as documented here.

Simply add pages/_error.js:

class Error extends React.Component {
  static getInitialProps({ res, err }) {
    const statusCode = res ? res.statusCode : err ? err.statusCode : null;
    return { statusCode };
  }

  render() {
    return (
      <p>
        {this.props.statusCode
          ? `An error ${this.props.statusCode} occurred on server (╯°□°)╯︵ ┻━┻`
          : "An error occurred on client"}
      </p>
    );
  }
}

export default Error;

Custom lambda handler

If you need to customize the lambda handler you can do so by providing a path to your own handler in the customHandler field. Note that it resolves the path to the custom handler relative to your next.config.js.

plugins:
  - serverless-nextjs-plugin

custom:
  serverless-nextjs:
    customHandler: ./handler.js

The custom handler needs to look something like this:

const compat = require("serverless-nextjs-plugin/aws-lambda-compat");

module.exports = page => {
  const handler = (event, context, callback) => {
    // do any stuff you like

    // this makes sure the next page renders
    compat(page)(event, context, callback);

    // do any other stuff you like
  };
  return handler;
};

All plugin configuration options

Plugin config key Default Value Description
nextConfigDir ./ Path to parent directory of next.config.js.
assetsBucketName <empty> Creates an S3 bucket with the name provided. The bucket will be used for uploading next static assets.
staticDir <empty> Directory with static assets to be uploaded to S3, typically a directory named static, but it can be any other name. Requires a bucket provided via the assetPrefix described above or the assetsBucketName plugin config.
routes [] Array of custom routes for the next pages or static assets.
customHandler <empty> Path to your own lambda handler.
uploadBuildAssets true In the unlikely event that you only want to upload the staticDir, set this to false.

Examples

See the examples/ directory.

Contributing

Please see the contributing guide.