Skip to navigation Skip to main content
11ty Logo Sustainability Fundraising
Eleventy
Eleventy Documentation
Stable
2.0.1
Canary
3.0.0-alpha.13
Toggle Menu
Eleventy 1.93s
Gatsby 29.05s

Serverless Added in v1.0.0

Contents

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.

What is Serverless? Jump to heading

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.

“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

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:

  1. Build template: Render in the build (preferred, start here)
  2. On-demand Builder template: Render on first request (use when your build gets beefy)
  3. Dynamic template: Render on every request (unlocks some new app-like use cases; can accept user input)

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!

INFO:

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 Boolean enables or disables the copying of 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, Eleventy 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 `page.data` 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

INFO:
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;

Replace the above with:

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):

netlify/functions/possum/**
!netlify/functions/possum/index.js

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: they use a permalink Object. So a possum serverless function permalink looks like this:

---
permalink:
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.

INFO:
NOTE: 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/:

---
permalink:
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. (Just make sure it maps 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.

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

Multiple Serverless Functions Jump to heading

Any number of serverless functions are allowed here.

---
permalink:
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.

---
permalink:
possum:
- /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.

INFO:
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.
---
permalink:
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: eleventy.serverless.path.id. (Here, id matches :id above).

WARNING:
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

These 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 specific to each template language.

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.

pagination:
data: authors
size: 1
serverless: eleventy.serverless.path.id
permalink:
possum: "/authors/:id/"

Eleventy fetches the value stored at eleventy.serverless.path.id (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 eleventy.serverless.path.id Global Data.
  3. Because pagination.serverless has the value "eleventy.serverless.path.id", 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.

WARNING:
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.

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

module.exports = function (eleventyConfig) {
eleventyConfig.addPlugin(EleventyServerlessBundlerPlugin, {
name: "possum",
copy: [".cache/eleventy-fetch/"],
});
};

And re-use the directory in your data files:

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

module.exports = async function () {
let options = {
// Use the same folder declared above
directory: ".cache/eleventy-fetch/",
};

if (process.env.ELEVENTY_SERVERLESS) {
// Infinite duration (until the next build)
options.duration = "*";

// Bypass writing new cache files, which would error in serverless mode
options.dryRun = true;
}

let result = await EleventyFetch("https://example.com/", options);
// …
};

Collections in Serverless Jump to heading

Eleventy Serverless typically operates on a subset of templates in your project. As such, collections that are outside the scope of the serverless build are not available in serverless mode. You have two options to workaround this limitation:

  1. Precompile your collections manually at build-time
  2. Build the data cascade for the project (no rendering required)

Precompile Collections at Build-Time Jump to heading

In this example we’ll build a static data file with collections data in it at build time and inject it into our serverless build at run time!

Consider a sidebarNav collection that populates a navigation menu (via the eleventy-navigation plugin).

Filename .eleventy.js
module.exports = function (eleventyConfig) {
eleventyConfig.addCollection("sidebarNav", function (collection) {
return collection.getAll().filter((item) => item.data?.eleventyNavigation);
});
};

This might be used in your templates (serverless or build) via {{ collections.sidebarNav | eleventyNavigation }}.

Now, the sidebarNav collection would not normally be available in a serverless context, because all of the templates that populate the menu are not in the scope of the serverless build. But we can generate a static copy of that collection for use in serverless mode. In fact, this is how the sidebar works on each (serverless) Author’s page (e.g. the one for @zachleat).

Consider the following Eleventy template which creates an array of collection-like entries for the sidebar navigation.

Expand to see code sample
Filename serverless-collections-export.11ty.js
exports.data = function () {
return {
// generate directly to the serverless bundle folder
permalink:
"./netlify/functions/serverless/_generated-serverless-collections.json",
permalinkBypassOutputDir: true,
eleventyExcludeFromCollections: true,
};
};

exports.render = function ({ collections }) {
let entries = [];
// Iterate over any items with the `sidebarNav` tag
for (let entry of collections.sidebarNav) {
entries.push({
data: {
page: entry.data.page,
eleventyNavigation: entry.data.eleventyNavigation,
},
});
}

return JSON.stringify(
{
sidebarNav: entries,
},
null,
2
);
};
INFO:
Note that it isn’t currently possible to serialize entry.data.collections to JSON as it may contain circular references. We hope to improve this in the future with a new collections API!

Inside of your serverless function file, you can import this file and use it directly:

Filename ./netlify/functions/possum/index.js
const precompiledCollections = require("./_generated-serverless-collections.json");

async function handler (event) {
let elev = new EleventyServerless("possum", {
path: event.path,
query: event.queryStringParameters,
precompiledCollections
});

// Some content truncated
};

Compile the data cascade for the project Jump to heading

As we have just learned, Eleventy Serverless operates on a subset of templates in your project. You can disable this subset scope with the singleTemplateScope option on the EleventyServerless class (defaults to true). Added in v2.0.0

This uses incremental builds with the new ignore initial build feature to only render one file (while building the larger data cascade for the project). The downside here is that while this is much friendlier to any use of collections on your templates, it is slower! Here are the conditions I’d expect folks to want to make this tradeoff:

Here’s how to enable this feature in your serverless function file:

Filename ./netlify/functions/possum/index.js
async function handler (event) {
let elev = new EleventyServerless("possum", {
path: event.path,
query: event.queryStringParameters,
singleTemplateScope: false, // true by default
});

// Some content truncated
};

At some point we may enable this feature by default if performance improves enough!

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

In this example we’re using a global data entry to control whether a downstream temple renders in serverless or build mode (at build time). In some more limited use cases this can solved using your hosting providers Redirects feature (e.g. on Netlify this means a netlify.toml or _redirects file).

If you want to make a decision at serverless runtime to render a build template, you’ll need to add logic to your serverless function to redirect to the build URL from the serverless template.

Filename .eleventy.js
module.exports = function (eleventyConfig) {
// Templates will generate via the Build
eleventyConfig.addGlobalData("runInServerlessMode", false);

// Or render in Serverless mode
eleventyConfig.addGlobalData("runInServerlessMode", true);
};

And then in your template files you can use this global data value with Computed Data to swap rendering modes:

Filename my-template-file.njk
---js
{
eleventyComputed: {
permalink: function({runInServerlessMode}) {
return {
[runInServerlessMode ? "serverless" : "build"]: "/"
}
}
}
}
---
Template Content goes here

For internal use

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 Official Plugins: