This guide shows you how to deploy a Next.js application on Layer0.

This Next.js example app uses server-side rendering and prefetching to provide lightening-fast transitions between pages.

For details on using the Next.js Commerce template with Layer0 refer to our Next.js Commerce Guide.

This framework has a connector developed for Layer0. See Connectors for more information.

Layer0 supports all of the most powerful features of Next.js 10, including:

  • Localization
  • Image Optimization
  • getStaticPaths (including fallback: (true|false|'blocking'))
  • getStaticProps (including revalidate)
  • getServerSideProps
  • getInitialProps

Layer0 only supports Node.js version 14 and higher

If you do not have Node.js installed on your system, download and install it from the official Node.js v14.x downloads page. Select the download that matches your operating system and run the installer. Note that the installer for Node.js will also install npm.

Note that while you can use any version of Node.js >= 14 locally, your app will run in Node 14 when deployed to the Layer0 cloud. Therefore we highly suggest using Node 14 for all development.

If you don't already have a Next.js application, you can create one using:

npm create next-app my-next-app

To prepare your Next.js application for deployment on Layer0:

npm install -g @layer0/cli

**Note When installing the Layer0 CLI globally in a virtual environment that has Node and NPM installed globally, you may run into permission issues. In that case, you can install the Layer0 CLI locally within you app using npm i -D @layer0/cli and running commands using ./node_modules/@layer0/cli instead of layer0.

If you run into permission issues while attempting to install the Layer0 CLI globally on your local development machine, these may be fixed by using nvm to manage Node and NPM.

cd my-next-app
layer0 init

This will automatically add all of the required dependencies and files to your project. These include:

  • The @layer0/core package - Allows you to declare routes and deploy your application on Layer0
  • The @layer0/next package - Provides router middleware that automatically adds Next.js pages and api routes to the Layer0 router.
  • The @layer0/prefetch package - Allows you to configure a service worker to prefetch and cache pages to improve browsing speed
  • The @layer0/react package - Provides a Prefetch component for prefetching pages
  • layer0.config.js
  • routes.js - A default routes file that sends all requests to Next.js. Update this file to add caching or proxy some URLs to a different origin.
  • sw/service-worker.js A service worker implemented using Workbox.

If your project does not have a next.config.js file, one will automatically be added when you run layer0 init. Doing so adds two plugins:

  • withLayer0 (required)
  • withServiceWorker (optional)

If your project already has a next.config.js file, you need to add these plugins yourself.

const { withLayer0, withServiceWorker } = require('@layer0/next/config')

module.exports = withLayer0(
  withServiceWorker({
    // Output sourcemaps so that stacktraces have original source filenames and line numbers when tailing
    // the logs in the Layer0 developer console.
    layer0SourceMaps: true,
  })
)

The withLayer0 optimizes the Next.js build for running on Layer0. It is required to deploy your application on Layer0 and accepts the following parameters:

  • layer0SourceMaps: Defaults to false. Set to true to add server-side sourcemaps so that so that stacktraces have original source filenames and line numbers when tailing the logs in the Layer0 developer console. This will increase the serverless bundle size but will not affect performance. If you find that your app exceeds the maximum serverless bundle size allowed by Layer0, you can disable this option to conserve space.

The withServiceWorker plugin builds a service worker from sw/service-worker.js that prefetches and caches all static JS assets and enables Layer0's prefetching functionality.

To simulate your app within Layer0 locally, run:

layer0 dev

Deploying requires an account on Layer0. Sign up here for free. Once you have an account, you can deploy to Layer0 by running the following in the root folder of your project:

layer0 deploy

See deploying for more information.

The layer0 init command adds a service worker based on Workbox at sw/service-worker.js. If you have an existing service worker that uses workbox, you can copy its contents into sw/service-worker.js and simply add the following to your service worker:

import { Prefetcher } from '@layer0/prefetch/sw'

new Prefetcher().route()

The above allows you to prefetch pages from Layer0's edge cache to greatly improve browsing speed. To prefetch a page, add the Prefetch component from @layer0/react to any Next Link element:

import { Prefetch } from '@layer0/react'
import Link from 'next/link'

export default function ProductListing({ products }) {
  return (
    <ul>
      {products.map((product, i) => (
        <li key={i}>
          <Link as={product.url} href="/p/[productId]" passHref>
            <Prefetch>
              <a>
                <img src={product.thumbnail} />
              </a>
            </Prefetch>
          </Link>
        </li>
      ))}
    </ul>
  )
}

The Prefetch component fetches data for the linked page from Layer0's edge cache and adds it to the service worker's cache when the link becomes visible in the viewport. When the user taps on the link, the page transition will be instantaneous because the browser won't need to fetch data from the network.

The Prefetch component assumes you're using getServerSideProps and will prefetch the data URL corresponding to the target page. If you need to prefetch a different url, you can do so using the url prop:

<Link as={product.url} href="/p/[productId]">
  <Prefetch url="/some/url/to/prefetch">
    <a>
      <img src={product.thumbnail} />
    </a>
  </Prefetch>
</Link>

Note that if you don't provide a url prop to Prefetch, you must specify the passHref prop on Link in order for the Prefetch component to know what URL to prefetch.

Layer0 supports Next.js's built-in routing scheme for both page and api routes, including Next.js 9's clean dynamic routes. The default routes.js file created by layer0 init sends all requests to Next.js via a fallback route:

// This file was automatically added by layer0 deploy.
// You should commit this file to source control.
const { Router } = require('@layer0/core/router')
const { nextRoutes } = require('@layer0/next')

module.exports = new Router()
  .get('/service-worker.js', ({ cache, serveStatic }) => {
    cache({
      edge: {
        maxAgeSeconds: 60 * 60 * 24 * 365,
      },
      browser: {
        maxAgeSeconds: 0,
      },
    })
    serveStatic('.next/static/service-worker.js')
  })
  .use(nextRoutes)

In the code above, the nextRoutes middleware adds all Next.js routes to the router based on the /pages directory. You can add additional routes before and after the middleware, for example to send some URLs to an alternate backend. This is useful for gradually replacing an existing site with a new Next.js app.

A popular use case is to fallback to a legacy site for any route that your Next.js app isn't configured to handle:

new Router().use(nextRoutes).fallback(({ proxy }) => proxy('legacy'))

To configure the legacy backend, use layer0.config.js:

module.exports = {
  backends: {
    legacy: {
      domainOrIp: process.env.LEGACY_BACKEND_DOMAIN || 'legacy.my-site.com',
      hostHeader: process.env.LEGACY_BACKEND_HOST_HEADER || 'legacy.my-site.com',
    },
  },
}

Using environment variables here allows you to configure different legacy domains for each Layer0 environment.

The nextRoutes middleware automatically adds routes for rewrites and redirects specified in next.config.js. Redirects are served directly from the network edge to maximize performance.

To render a specific page, use the renderNextPage function:

const { Router } = require('@layer0/core/router')
const { renderNextPage, nextRoutes } = require('@layer0/next')

module.exports = new Router()
  .get('/some/vanity/url/:p', res => {
    renderNextPage('/p/[productId]', res, params => ({ productId: params.p }))
  })
  .use(nextRoutes)

The renderNextPage function takes the following parameters:

  • nextRoute - String The next.js route path
  • res - ResponseWriter The ResponseWriter passed to your route handler
  • params - Object|Function An object containing query params to provide to the next page, or a function that takes the route's path params and the request and returns a params object.

You can explicitly render the Next.js 404 page using nextRoutes.render404(res):

const { Router } = require('@layer0/core/router')
const { renderNextPage, nextRoutes } = require('@layer0/next')

module.exports = new Router()
  .get('/some/missing/page', res => {
    nextRoutes.render404(res)
  })
  .use(nextRoutes)

Usually Next.js requires 404.js to be a static page. Layer0 enables you to render a specific page when no other route is matched using router.fallback:

const { Router } = require('@layer0/core/router')
const { renderNextPage, nextRoutes } = require('@layer0/next')

module.exports = new Router().use(nextRoutes).fallback(res => {
  renderNextPage('/not-found', res) // render pages/not-found.js, which can be dynamic (using getInitialProps or getServerSideProps)
})

The easiest way to add edge caching to your Next.js app is to add caching routes before the middleware. For example, imagine you have /pages/p/[productId].js. Here's how you can SSR responses as well as cache calls to getServerSideProps:

new Router()
  // Products - SSR
  .get('/p/:productId', ({ cache }) => {
    cache({
      browser: {
        maxAgeSeconds: 0,
      },
      edge: {
        maxAgeSeconds: 60 * 60 * 24,
        staleWhileRevalidateSeconds: 60 * 60,
      },
    })
  })
  // Products - getServerSideProps
  .get('/_next/data/:version/p/:productId.json', ({ cache }) => {
    cache({
      browser: {
        maxAgeSeconds: 0,
        serviceWorkerSeconds: 60 * 60 * 24,
      },
      edge: {
        maxAgeSeconds: 60 * 60 * 24,
        staleWhileRevalidateSeconds: 60 * 60,
      },
    })
  })
  .use(nextRoutes)

By default, Next.js adds a cache-control: private, no-cache, no-store, must-revalidate header to all responses from getServerSideProps. The presence of private would prevent Layer0 from caching the response, so nextRoutes middleware from @layer0/next automatically removes the private portion of the header to enable caching at edge. If you want your responses to be private, you need to specify a cache-control header using the router:

new Router().get('/my-private-page', ({ setResponseHeader }) => {
  setResponseHeader('cache-control', 'private, no-cache, no-store, must-revalidate')
})

Doing so will prevent other CDNs running in front of Layer0 from caching the response.

As of Next.js v10.0.6, Webpack 4 is still used by default. You can upgrade to Webpack 5 by making the following changes to your app:

Add "webpack": "^5.0.0" to resolutions:

"resolutions": {
  "webpack": "^5.0.0"
}

Add the following to next.config.js:

future: {
  webpack5: true,
}

Then run yarn install followed by layer0 build to verify that your app builds successfully using Webpack 5.

Some additional notes:

  • In order to use Webpack 5 you must use yarn to install dependencies. NPM does not support resolutions in package.json.
  • Webpack 5 contains many breaking changes, so it is possible that you'll need to make additional changes to the webpack config via next.config.js to get your app to build successfully.
  • You may run into this error: UnhandledPromiseRejectionWarning: TypeError: dependency.getCondition is not a function. You can fix this by adding next-offline as a dependency using npm i -D next-offline or yarn add --dev next-offline.
  • You'll also see some deprecation warnings, like these, which are fine, as long as layer0 build is successful:
(node:95329) [DEP_WEBPACK_SINGLE_ENTRY_PLUGIN] DeprecationWarning: SingleEntryPlugin was renamed to EntryPlugin
info  - Creating an optimized production build...
(node:95339) [DEP_WEBPACK_SINGLE_ENTRY_PLUGIN] DeprecationWarning: SingleEntryPlugin was renamed to EntryPlugin
> Creating service worker...
(node:95339) [DEP_WEBPACK_COMPILATION_ASSETS] DeprecationWarning: Compilation.assets will be frozen in future, all modifications are deprecated.
BREAKING CHANGE: No more changes should happen to Compilation.assets after sealing the Compilation.
        Do changes to assets earlier, e. g. in Compilation.hooks.processAssets.
        Make sure to select an appropriate stage from Compilation.PROCESS_ASSETS_STAGE_*.
> Optimizing serverless functions (Webpack 5)
(node:95339) [DEP_WEBPACK_CHUNK_HAS_ENTRY_MODULE] DeprecationWarning: Chunk.hasEntryModule: Use new ChunkGraph API