Stand with Ukraine 🇺🇦
The possum is Eleventy’s mascot

Eleventy Documentation


Serverless New in v1.0.0 Jump to heading

A plugin to run Eleventy in a serverless function for server side rendering (e.g. Previews in your CMS) and/or in very large sites with On-demand Builders.

Expand for contents

What is Serverless? Jump to heading

You can read more about serverless on the eponymous Serverless microsite from CSS-Tricks.

“You can write a JavaScript function that you run and receive a response from by hitting a URL.”—The Power of Serverless from Chris Coyier

Eleventy Serverless complements your existing statically generated site by running one or more template files at request time to generate dynamic pages. It can unlock many new use cases to move beyond static files into dynamically generated content.

Rendering Modes Jump to heading

These different use cases and rendering modes are important to understand and have different trade-offs and risks associated with them. In a Jamstack world, the order of preference should be:

Build-time (non-serverless) templates should be the preferred rendering mode. They are the most reliable and stable. A failure in a build generated template will fail your deployment and prevent user-facing errors in production.

For On-demand Builders and Dynamic templates, rendering failures will not fail your deployment and as such incur more risk. Dynamic templates must also be closely performance monitored—unlike build templates, a slow render in a dynamic template means a slow web site for end-users.

Demos and Community Resources Jump to heading

Usage Jump to heading

Step 1: Add the Bundler Plugin Jump to heading

This plugin is bundled with Eleventy core and doesn’t require you to npm install anything. Use the addPlugin configuration API to add it to your Eleventy config file (probably .eleventy.js):

Filename .eleventy.js
const { EleventyServerlessBundlerPlugin } = require("@11ty/eleventy");

module.exports = function(eleventyConfig) {
eleventyConfig.addPlugin(EleventyServerlessBundlerPlugin, {
name: "possum", // The serverless function name from your permalink object
functionsDir: "./netlify/functions/",

You can add the Bundler plugin more than once to accommodate multiple Eleventy Serverless rendering modes simultaneously. Your templates can render in multiple modes!

You won’t need to set up bundler plugins for every individual template, but instead you’ll want to use one plugin for each rendering mode.

* Dynamic pages via server side rendering will need one plugin, perhaps named onrequest or dynamic.
* Delayed rendering using On-demand Builders will need another plugin, perhaps named onfirstrequest or odb.

Bundler Options Jump to heading

Key: Default Value Description
name (Required) Above we used "possum" but you should use serverless if you’re not sure what to call it.
functionsDir: "./functions/" The directory that holds your serverless functions. Netlify supports ./netlify/functions/ without configuration.
copy: [] an Array of extra files to bundle with your serverless function. We copy your templates files for you but this is useful for additional files that may be used at build-time. Array entries can be:
  • a String for a single file or a directory. e.g. "logo.svg" or "folder/"
  • an Object with from and to keys to change the output directory inside your bundle. e.g. { from: ".cache", to: "cache" }
redirects: "netlify-toml" How we manage your serverless redirects. This will add serverless redirects to your netlify.toml file and remove stale routes for you.
  • redirects: false will skip this entirely.
  • redirects: "netlify-toml" (default) to use Netlify Functions.
  • redirects: "netlify-toml-functions" (alias for netlify-toml)
  • redirects: "netlify-toml-builders" to use Netlify On-demand Builders
  • Write your own: Use a custom Function instead of a String: function(name, outputMap). Don’t forget to handle removal of stale routes too!
inputDir: "." The Eleventy input directory (containing your Eleventy templates). This is no longer necessary. Eleventy injects this for you automatically.
config: function(eleventyConfig) {} Run your own custom Eleventy Configuration API code inside of the serverless function. Useful for a wide variety of things, but was added to facilitate developers wiring up additional serverless information from the event object to templates using eleventyConfig.addGlobalData. For example, wire up cookies using event.headers.cookie or form post data using event.body.
Advanced Options:
copyEnabled: true Useful for local development, this is a Boolean to enable or disable copying project files into your serverless bundle. File copying is pretty cheap so you will likely want to leave this as-is.
  • Try copyEnabled: process.env.NODE_ENV !== "development" (and set environment variables when running Eleventy locally e.g. NODE_ENV=development npx @11ty/eleventy)
copyOptions: {} Advanced configuration of copy behavior, consult the recursive-copy docs on NPM. You probably won’t need this.
excludeDependencies: [] Array of dependencies explicitly excluded from the list found in your configuration file and global data files. These will not be visible to the serverless bundler.

Your Generated Serverless Function Jump to heading

Based on your plugin configuration, we will create your initial boilerplate serverless function for you. After initial creation, this serverless function code is managed by you. Here is an over-simplified version for educational purposes only:

Limitation ⚠️ This snippet is for educational purposes only—don’t copy and paste it!
const { EleventyServerless } = require("@11ty/eleventy");

async function handler (event) {
let elev = new EleventyServerless("possum", {
path: event.path, // required, the URL path
query: event.queryStringParameters, // optional

try {
// returns the HTML for the Eleventy template that matches to the URL
// Can use with `eleventyConfig.dataFilterSelectors` to put data cascade data into `` here.
let [page] = await elev.getOutput();
let html = page.content;

return {
statusCode: 200,
body: html
} catch(e) {
return {
statusCode: 500,
body: JSON.stringify({ error: e.message })

exports.handler = handler;

Read more about dataFilterSelectors.

Use with On-demand Builders Jump to heading

Note: As of right now, On-demand Builders are a Netlify specific feature.

If, instead, you want to use an On-demand Builder to render the content on first-request and cache at the CDN for later requests, you will need to do two things:

Thing 1: Swap the export in your template (and npm install @netlify/functions):

exports.handler = handler; // turns into:

const { builder } = require("@netlify/functions");
exports.handler = builder(handler);

Thing 2: Use redirects: "netlify-toml-builders" in your bundler config.

The redirects need to point to /.netlify/builders/ instead of /.netlify/functions so if you have written your own redirects handler, you’ll need to update that.

Step 2: Add to .gitignore Jump to heading

Add the following rules to your .gitignore file (where possum is the name of your serverless function name):


Making a template file dynamic is as easy as changing your permalink. You might be familiar with this well-worn permalink syntax:

permalink: /build-generated-path/

Serverless templates introduce a slight change and use a permalink Object, so a possum serverless function permalink looks like this:

possum: /dynamic-path/

These objects can be set anywhere in the data cascade (even inside of Computed Data). Here’s an example of a serverless URL for our possum serverless function. Any requests to /dynamic-path/ will now be generated at request-time.

build is the only reserved key in a permalink Object. If you want your template to continue to be built at build-time, use the build key. The following is functionally equivalent to permalink: /build-generated-path/:

build: /build-generated-path/

Anything other than build is assumed to map to a serverless function. We used the name possum, but you can use any string and it should map to the name you passed to the Bundler Plugin above.

Build-time and Serverless Jump to heading

You can mix both build and possum in the same permalink object! This will make the same input file render both at build-time and in a serverless function. This might be useful when you want a specific URL for a CMS preview but still want the production build to use full build-time templates.

build: /build-generated-path/
possum: /dynamic-path/

Multiple Serverless Functions Jump to heading

Any number of serverless functions are allowed here.

possum: /dynamic-path/
quokka: /some-other-dynamic-path/

Multiple URLs per Serverless Function Jump to heading

If you want to drive multiple URLs with one serverless template, pass in an Array of URLs.

- /dynamic-path/
- /some-other-dynamic-path/

Dynamic Slugs and Serverless Global Data Jump to heading

Perhaps most interestingly, this works with dynamic URLs too. This will work with any syntax supported by the path-to-regexp package.

Astute users of the 1.0 canary prereleases will note that starting in Beta 1, this package changed from url-pattern to path-to-regexp. Read more at Issue 1988.
possum: /dynamic-path/:id/

This will match any requested URL that fits the /dynamic-path/ followed by an open-ended folder name (e.g. /dynamic-path/hello/ or /dynamic-path/goodbye/). The above uses :id for the key name. When the templates are rendered, the key name puts the matched path String value for id into your Serverless Global Data in the Data Cascade at: (id here matches :id above).

These should be treated as potentially malicious user input and you must escape these if you use them in templates. Read more about Escaping User Input.

Here’s what your Serverless Global Data might look like:

eleventy: {
serverless: {
path: {
id: "hello" // from /dynamic-path/hello/
//id: "goodbye" // from /dynamic-path/goodbye/

Escaping User Input Jump to heading

When using dynamic slugs or query parameters, the values here should be treated as potentially malicious user input and you must escape these if you use them in templates. The way to do this is template language specific.

Advanced Jump to heading

Dynamic Slugs to Subset Your Pagination Jump to heading

Use the new serverless option in pagination to slice up your paginated data set using a dynamic slug! Here’s how we use it for the Eleventy Author Pages.

data: authors
size: 1
possum: "/authors/:id/"

Eleventy fetches the value stored at (using lodash get) and does an additional get on the pagination data in authors.

For example:

  1. A request is made to /authors/zachleat/
  2. The dynamic URL slug for the possum serverless function /authors/:id/ matches zachleat to :id. This sets "zachleat" in the Global Data.
  3. Because pagination.serverless has the value "", we use lodash.get to select the key "zachleat" from Global Data.
  4. An additional lodash.get(authors, "zachleat") returns a single chunk of data for one author.
  5. Pagination only operates on that one selected page for rendering.

Input via Query Parameters Jump to heading

In Dynamic Templates (not On-demand Builders), you can use query parameters as user input. Query parameters are available in the eleventy.serverless.query object.

These should be treated as potentially malicious user input and you must escape these if you use them in templates. Read more about Escaping User Input.

/my-url/?id=hello might look like this in the Data Cascade of a dynamic template:

eleventy: {
serverless: {
query: {
id: "hello" // from /my-url/?id=hello
//id: "goodbye" // from /my-url/?id=goodbye

Re-use build-time cache from the Fetch plugin Jump to heading

To speed up serverless rendering and avoid requests to external sources, you can re-use the cache folder from your build! First we’ll need to copy the cache folder into our bundle and rename it without the leading dot (the bundler ignores dot prefixed files and folders).

Filename .eleventy.js
const { EleventyServerlessBundlerPlugin } = require("@11ty/eleventy");

module.exports = function(eleventyConfig) {
eleventyConfig.addPlugin(EleventyServerlessBundlerPlugin, {
name: "possum",
copy: [
// files/directories that start with a dot
// are not bundled by default
{ from: ".cache", to: "cache" }

And then in your data file, overwrite the duration and directory to point to this new folder:

Filename _data/github.js
const EleventyFetch = require("@11ty/eleventy-fetch");

module.exports = async function() {
let options = {};

if(process.env.ELEVENTY_SERVERLESS) {
// Infinite duration (until the next build)
options.duration = "*";
// Instead of ".cache" default because files/directories
// that start with a dot are not bundled by default = "cache";

let result = await EleventyFetch("", options);
// …

Re-use build-time Collections Jump to heading

Documentation in progress

Swap to Dynamic using the Data Cascade and eleventyComputed Jump to heading

Documentation in progress

How do Dynamic Templates and tags work together? Jump to heading

Documentation in progress

For internal use Jump to heading

Expand to view Dependency Bundle Sizes
Bundle size Package name
Bundle size for @11ty/eleventy @11ty/eleventy
Bundle size for @11ty/eleventy @11ty/eleventy@canary
Bundle size for @11ty/eleventy-img @11ty/eleventy-img
Bundle size for @11ty/eleventy-fetch @11ty/eleventy-fetch
Bundle size for @11ty/eleventy-plugin-syntaxhighlight @11ty/eleventy-plugin-syntaxhighlight
Bundle size for @11ty/eleventy-navigation @11ty/eleventy-navigation
Bundle size for @11ty/eleventy-plugin-vue @11ty/eleventy-plugin-vue
Bundle size for @11ty/eleventy-plugin-rss @11ty/eleventy-plugin-rss

Other pages in Plugins: