Menu
- Why Eleventy?
- Get Started
- Community
- Working with Templates
- Using Data
- Configuration
- Template Languages
- Plugins
- API Services
- Release History
- Advanced
1.93s
22.90s
Serverless Added in v1.0.0
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.
- Server side rendering for fully dynamic pages, e.g. content preview in your Content Management System.
- Rendering of individual templates using On-demand Builders, useful to improve large site build times both locally and in production.
“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:
- Build template: Render in the build (preferred, start here)
- On-demand Builder template: Render on first request (use when your build gets beefy)
- 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
):
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
ordynamic
). - Delayed rendering using On-demand Builders will need another plugin (perhaps named
onfirstrequest
orodb
).
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:
|
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.
|
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.)
|
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:
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
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
Step 3: Use a permalink
Object
Jump to heading
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.
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.
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).
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.
- Liquid has both an
escape
andescape_once
filter. - Nunjucks has autoescape turned on by default. (If you’ve disabled it, you can use the
escape
filter.) - Other template languages: Read more in the Layouts documentation, which has other template languages’ methods for both escaped and unescaped output.
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:
- A request is made to
/authors/zachleat/
- The dynamic URL slug for the
possum
serverless function/authors/:id/
matcheszachleat
to:id
. This sets"zachleat"
in theeleventy.serverless.path.id
Global Data. - Because
pagination.serverless
has the value"eleventy.serverless.path.id"
, we use lodash.get to select the key"zachleat"
from Global Data. - An additional
lodash.get(authors, "zachleat")
returns a single chunk of data for one author. - 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.
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.
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:
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:
- Precompile your collections manually at build-time
- 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).
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
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);
};
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:
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:
- If your project is small/fast enough and you don’t want to spend the extra development effort.
- If your project is larger but you’re using On-demand Builders where the extra rendering cost is only paid once.
- For larger projects I would not recommend use of
singleTemplateScope: false
in a dynamic template that renders with each request.
Here’s how to enable this feature in your serverless function file:
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.
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:
---js
{
eleventyComputed: {
permalink: function({runInServerlessMode}) {
return {
[runInServerlessMode ? "serverless" : "build"]: "/"
}
}
}
}
---
Template Content goes here
- If you’re here you may also be interested in the Eleventy Serverless OAuth demo
For internal use
Dependency Bundle Sizes
Bundle size | Package name |
---|---|
@11ty/eleventy |
|
@11ty/eleventy@canary |
|
@11ty/eleventy-img |
|
@11ty/eleventy-fetch |
|
@11ty/eleventy-plugin-syntaxhighlight |
|
@11ty/eleventy-navigation |
|
@11ty/eleventy-plugin-vue |
|
@11ty/eleventy-plugin-rss |