This guide shows you how to deploy a Next.js application to Edgio.
Example
Next.js Commerce
For details on using the Next.js Commerce template with Edgio, refer to our Next.js Commerce Guide.
Connector
This framework has a connector developed for Edgio. See Connectors for more information.
Supported Versions
Edgio supports Next version 9 through 12.
Supported Features
Edgio supports all of the most powerful features of Next.js, including:
- SSG
- SSR
- ISG
- ISR
- Localization
- Image Optimization
getStaticPaths
(includingfallback: (true|false|'blocking')
)getStaticProps
(includingrevalidate
)getServerSideProps
getInitialProps
System Requirements
Sign up for Edgio
Deploying requires an account on Edgio. Sign up here for free.
Install the Edgio CLI
If you have not already done so, install the Edgio CLI.
With npm
:
1npm i -g @layer0/cli
With yarn
:
1yarn global add @layer0/cli
When installing the Edgio 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 Edgio CLI locally within your app using npm i -D @layer0/cli
and running commands using ./node_modules/@layer0/cli
instead of 0
.
If you run into permission issues while attempting to install the Edgio CLI globally on your local development machine, these may be fixed by using nvm to manage Node and NPM.
Getting Started
Create a Next.js Application
If you don’t already have a Next.js application, you can create one using:
1npx create-next-app@latest
Initializing your Project
Initialize your project for use with Edgio by running the following command in your project’s root directory:
1cd my-next-app20 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 to Edgio. - The
@layer0/next
package - Provides router middleware that automatically adds Next.js pages and api routes to the Edgio 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 aPrefetch
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.
Next.js Config Plugins
If your project does not have a next.config.js
file, one will automatically be added when you run 0 init
. Doing so adds two plugins:
withLayer0
(required)withServiceWorker
(optional)
If your project already has this config file, you need to add these plugins yourself.
1const { withLayer0, withServiceWorker } = require('@layer0/next/config')23module.exports = withLayer0(4 withServiceWorker({5 // Output source maps so that stack traces have original source filenames and line numbers when tailing6 // the logs in the Edgio developer console.7 layer0SourceMaps: true,8 })9)
withLayer0
The withLayer0
plugin optimizes the Next.js build for running on Edgio. It is required to deploy your application on Edgio and accepts the following parameters:
layer0SourceMaps
: Defaults tofalse
. Set totrue
to add server-side source maps so that stack traces have original source filenames and line numbers when tailing the logs in the Edgio 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 Edgio, you can disable this option to conserve space.
withServiceWorker
The withServiceWorker
plugin builds a service worker from sw/service-worker.js
that prefetches and caches all static JS assets and enables Edgio’s prefetching functionality.
Edgio Devtools
By default, Devtools are enabled on production builds of Next.js with Edgio. To disable devtools in production, add the disableLayer0DevTools
flag:
1const { withLayer0, withServiceWorker } = require('@layer0/next/config')23module.exports = withLayer0(4 withServiceWorker({5 // Output source maps so that stack traces have original source filenames and line numbers when tailing6 // the logs in the Edgio developer console.7 layer0SourceMaps: true,8 // Don't include Edgio Devtools in production9 // More on Edgio Devtools at https://docs.layer0.co/guides/devtools10 disableLayer0DevTools: true,11 })12)
Running Locally
Test your app with the App Platform on your local machine by running the following command in your project’s root directory:
10 dev
Deploying
Deploy your app to the App Platform by running the following command in your project’s root directory:
10 deploy
See deploying for more information.
Prefetching
The 0 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:
1import { Prefetcher } from '@layer0/prefetch/sw'23new Prefetcher().route()
Adding the Edgio Service Worker
To add the Edgio service worker to your app, call the install
function from @layer0/prefetch/window
in a useEffect
hook when the app first loads. For example, you can alter the pages/_app.js
in your Next.js app as follows:
1// pages/_app.js2import { useEffect } from 'react'3import { install } from '@layer0/prefetch/window'45const MyApp = ({ Component, pageProps }) => {6 useEffect(() => {7 if (process.env.NODE_ENV === 'production') {8 install()9 }10 }, [])11}
The code above allows you to prefetch pages from Edgio’s edge cache to greatly improve browsing speed. To prefetch a page, add the Prefetch
component from @layer0/react
to any Next Link
element. The example below shows you how to prefetch JSON data from getServerSideProps
or getStaticProps
using the createNextDataUrl
function from @layer0/next/client
.
1import { Prefetch } from '@layer0/react'2import Link from 'next/link'3import { createNextDataURL } from '@layer0/next/client'45export default function ProductListing({ products }) {6 return (7 <ul>8 {products.map((product, i) => (9 <li key={i}>10 <Link href={product.url} passHref>11 <Prefetch12 url={createNextDataURL({13 href: product.url,14 routeParams: {15 // keys must match the param names in your next page routes16 // So for example if your product page is /products/[productId].js:17 productId: product.id,18 },19 })}20 >21 <a>22 <img src={product.thumbnail} />23 </a>24 </Prefetch>25 </Link>26 </li>27 ))}28 </ul>29 )30}
The Prefetch
component fetches data for the linked page from Edgio’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:
1<Link as={product.url} href="/p/[productId]">2 <Prefetch url="/some/url/to/prefetch">3 <a>4 <img src={product.thumbnail} />5 </a>6 </Prefetch>7</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 which URL to prefetch.
Routing
Edgio 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 0 init
sends all requests to Next.js via a fallback route:
1// This file was automatically added by 0 deploy.2// You should commit this file to source control.3const { Router } = require('@layer0/core/router')4const { nextRoutes } = require('@layer0/next')56module.exports = new Router()7 // Prevent search engine bot(s) from indexing8 // Read more on: https://docs.layer0.co/guides/cookbook#blocking-search-engine-crawlers9 .noIndexPermalink()10 .get('/service-worker.js', ({ cache, serveStatic }) => {11 cache({12 edge: {13 maxAgeSeconds: 60 * 60 * 24 * 365,14 },15 browser: {16 maxAgeSeconds: 0,17 },18 })19 serveStatic('.next/static/service-worker.js')20 })21 .use(nextRoutes)
nextRoutes middleware
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, you can choose 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:
1new Router().use(nextRoutes).fallback(({ proxy }) => proxy('legacy'))
To configure the legacy backend, use layer0.config.js:
1module.exports = {2 backends: {3 legacy: {4 domainOrIp: process.env.LEGACY_BACKEND_DOMAIN || 'legacy.my-site.com',5 hostHeader: process.env.LEGACY_BACKEND_HOST_HEADER || 'legacy.my-site.com',6 },7 },8}
Using environment variables here allows you to configure different legacy domains for each Edgio environment.
rewrites and redirects
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.
Explicit Routes
To render a specific page, use the renderNextPage
function:
1const { Router } = require('@layer0/core/router')2const { renderNextPage, nextRoutes } = require('@layer0/next')34module.exports = new Router()5 .get('/some/vanity/url/:p', res => {6 renderNextPage('/p/[productId]', res, params => ({ productId: params.p }))7 })8 .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
Rendering the 404 page
You can explicitly render the Next.js 404 page using nextRoutes.render404(res)
:
1const { Router } = require('@layer0/core/router')2const { renderNextPage, nextRoutes } = require('@layer0/next')34module.exports = new Router()5 .get('/some/missing/page', res => {6 nextRoutes.render404(res)7 })8 .use(nextRoutes)
Dynamic Fallback Route
Usually Next.js requires 404.js to be a static page. Edgio enables you to render a specific page when no other route is matched using router.fallback
:
1const { Router } = require('@layer0/core/router')2const { renderNextPage, nextRoutes } = require('@layer0/next')34module.exports = new Router().use(nextRoutes).fallback(res => {5 renderNextPage('/not-found', res) // render pages/not-found.js, which can be dynamic (using getInitialProps or getServerSideProps)6})
Caching
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
:
1new Router()2 // Prevent search engine bot(s) from indexing3 // Read more on: https://docs.layer0.co/guides/cookbook#blocking-search-engine-crawlers4 .noIndexPermalink()5 // Products - SSR6 .get('/p/:productId', ({ cache }) => {7 cache({8 browser: {9 maxAgeSeconds: 0,10 },11 edge: {12 maxAgeSeconds: 60 * 60 * 24,13 staleWhileRevalidateSeconds: 60 * 60,14 },15 })16 })17 // Products - getServerSideProps18 .get('/_next/data/:version/p/:productId.json', ({ cache }) => {19 cache({20 browser: {21 maxAgeSeconds: 0,22 serviceWorkerSeconds: 60 * 60 * 24,23 },24 edge: {25 maxAgeSeconds: 60 * 60 * 24,26 staleWhileRevalidateSeconds: 60 * 60,27 },28 })29 })30 .use(nextRoutes)
Preventing Next.js pages from being cached by other CDNs
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 Edgio from caching the response, so nextRoutes
middleware from @layer0/next
automatically removes the private
portion of the header to enable caching at the edge. If you want your responses to be private, you need to specify a cache-control
header using the router:
1new Router().get('/my-private-page', ({ setResponseHeader }) => {2 setResponseHeader('cache-control', 'private, no-cache, no-store, must-revalidate')3})
Doing so will prevent other CDNs running in front of Edgio from caching the response.
Building with Webpack 5
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:
package.json
Add "webpack": "^5.0.0"
to resolutions
:
1"resolutions": {2 "webpack": "^5.0.0"3}
next.config.js
Add the following to next.config.js
:
1future: {2 webpack5: true,3}
Then run yarn install
followed by 0 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 addingnext-offline
as a dependency usingnpm i -D next-offline
oryarn add --dev next-offline
. - You’ll also see some deprecation warnings, like these, which are fine, as long as
0 build
is successful:
1(node:95329) [DEP_WEBPACK_SINGLE_ENTRY_PLUGIN] DeprecationWarning: SingleEntryPlugin was renamed to EntryPlugin2info - Creating an optimized production build...3(node:95339) [DEP_WEBPACK_SINGLE_ENTRY_PLUGIN] DeprecationWarning: SingleEntryPlugin was renamed to EntryPlugin4> Creating service worker...5(node:95339) [DEP_WEBPACK_COMPILATION_ASSETS] DeprecationWarning: Compilation.assets will be frozen in future, all modifications are deprecated.6BREAKING CHANGE: No more changes should happen to Compilation.assets after sealing the Compilation.7 Do changes to assets earlier, e. g. in Compilation.hooks.processAssets.8 Make sure to select an appropriate stage from Compilation.PROCESS_ASSETS_STAGE_*.9> Optimizing serverless functions (Webpack 5)10(node:95339) [DEP_WEBPACK_CHUNK_HAS_ENTRY_MODULE] DeprecationWarning: Chunk.hasEntryModule: Use new ChunkGraph API
Using next-i18next
The next-i18next package is a popular solution for adding localization to Next.js apps. It has some issues when running in serverless deployments, but you can work around these:
First, you need to not use the default name for the next-i18next.config.js
file. We recommend renaming it i18next.config.js
. When you use the default name, next-i18next will try to load the config when the serverless function starts and since it is not bundled with the app, it will fail.
Then, you need to explicitly provide the config to appWithTranslation
and serverSideTranslations
.
So in your pages/_app.js
:
1export default appWithTranslation(MyApp, require('../i18next.config')) // <~ need to explicitly pass the config here
and in your pages:
1export async function getStaticProps({ locale }) {2 return {3 props: {4 ...(await serverSideTranslations(locale, ['common', 'footer'], require('../i18next.config'))), // <~ need to explicitly pass the config here.5 // Will be passed to the page component as props6 },7 }8}
Make sure you also import the config correctly with the new name into your next.config.js
:
1const { withLayer0, withServiceWorker } = require('@layer0/next/config')2const { i18n } = require('./i18next.config')34module.exports = withLayer0(5 withServiceWorker({6 // Output source maps so that stack traces have original source filenames and line numbers when tailing7 // the logs in the Edgio developer console.8 layer0SourceMaps: true,9 i18n,10 }),11)
Finally, you will need to update your layer0.config.js
to includeFiles where the locale files are stored. Example using the default of /public
:
1module.exports = {2 connector: '@layer0/next',3 includeFiles: {4 public: true,5 },6}
A working example app can be found here.
Using experimental-serverless-trace
As of v3.16.6, Next.js apps built with Edgio will use the experimental-serverless-trace
target by default. The serverless target does not support most modern Next.js features like preview mode, revalidate, fallback. For backwards compatibility reasons, the serverless target will still be supported.
At build time, Edgio will run a trace on your application code and find only the required modules needed to run your production application, then add those to the deployment bundle.
Next.js version 12 and Next.js Middleware (BETA)
As of Next.js version 12 the serverless
and experimental-serverless-trace
targets have been deprecated and no new features will be supported for these targets. This includes Next.js Middleware (beta) and React component streaming (alpha). The @layer0/next
connector has historically utilized the serverless
and experimental-serverless-trace
targets to create a suitable build output for a serverless runtime.
As of v4.13.0 Edgio packages, Next.js apps using Next.js versions 12 or higher can opt into using the default server
target by explicitly setting target: 'server'
in the next.config.js
file. The @layer0/next
connector will then create a minimal server bundle with requests delegated to a Next.js server instance instead of rendering via serverless page functions. When opting into the server
target you can use Next.js Middleware.
Future versions of Edgio when using Next.js 12 or higher will utilize the server
target by default.
Next.js 12 with server target deprecations
NextRouter.render404
and renderNextPage
with specifying a page to render are retired when using Next.js 12+ and the server
target. Requests are delegated to a Next.js server instance which will handle determining which page to render based on the request. Prior use cases should now be achieved via using Next.js redirects and rewrites.
Middleware caveats
When using Next.js Middleware it should be noted that the middleware functions are only executed at the serverless layer, ie after edge cache. Middleware that you want to execute on each request needs to have caching disabled explicitly for the route that the middleware is configured to run for. Some Middleware use cases such as rewriting the request to another route would be fine to cache. These use cases need to be evaluated on a per route basis with caching enabled/disabled based on the desired result.