Organizing the Eleventy config file

Published:
Last edit:
Reading time:
8 min.

Eleventy comes with basic defaults already set up for you. That means you don’t have to do anything to start working: the output folder defaults to _site, and Eleventy looks for your source files in the root directory.

This is okay for very small projects. However, most of my projects get quite large, and they need customizations and dependencies. I also have personal preferences, and Eleventy is quite open about that - you can arrange your input files and folders as you wish and call them whatever you like. Of course, Eleventy must be made aware of these settings.

This article is all about organizing your Eleventy configuration file.

Let’s begin!

Skip table of contents

Table of contents

Create an eleventy.js config file

Add a new file in the root directory called .eleventy.js (as of Eleventy 2.0 it may also be called eleventy.config.js.).

Let’s make a small adjustment in the folder structure first.

module.exports = function (eleventyConfig) {
return {
dir: {
input: 'src',
output: 'dist',
includes: '_includes',
layouts: '_layouts'
}
};
};

Our output folder is now dist, and all our source files go into src.
Also, due to my personal preference, I pull the layouts out of _includes, where they usually live, and make sure that the directories are now next to each other.

This leaves our root to all the files that absolutely have to be there - like package.json and README.md as well as config files of other modules you use in your project.

Structuring the input folder

Create a folder called src in your root directory.

Though we are not going to touch most of the folders, this is what your usual website-project might look like:


├── src
│ │
│ ├── _data
│ ├── _includes
│ ├── _layouts
│ ├── assets
│ ├── pages
│ ├── posts
│ ├── projects

pages is for your static pages like index.md, about.md, etc., posts contains your blog articles, and projects is just another collection folder we create to make it worthwhile to get the logic out of eleventy.js.

… Because you can configure all your collections, shortcodes and filters right there. The official Eleventy starter is pretty simple and does it like that.

Outsourcing customizations

I want my projects to grow freely without worrying that my config file is getting too cluttered. So I deal with customizations elsewhere and import only the return value of my functions.

My preference is to create a new folder in the root directory, called config.

Another common approach is adding a folder to src called _11ty. I found this in Nicolas Hoizeys’ starter pack11ty. You can name the folder whatever you want and put it wherever you like.
In this case, I will go on pretending you made a folder called config in your root directory.

We don’t need to tell Eleventy about the existence of this folder. We just use it export our return values and import them into .eleventy.js.

I introduce two ways to handle this, using collections as an example.

Method 1: Import file and loop over the collection keys

Create a file named collections.js in your config folder.
Now define all the collections you want to use:

module.exports = {
posts: function (collectionApi) {
return collectionApi.getFilteredByGlob('src/posts/**/*.md');
},
projects: function (collectionApi) {
return collectionApi.getFilteredByGlob('src/projects/**/*.md');
}
};

Your eleventy.js now looks like this:

// Importing from config
const collections = require('./config/collections.js');

module.exports = eleventyConfig => {
// Collections
Object.keys(collections).forEach(collectionName => {
eleventyConfig.addCollection(collectionName, collections[collectionName]);
});

return {
dir: {
input: 'src',
output: 'dist',
includes: '_includes',
layouts: '_layouts'
}
};
};

We loop through all the collections that are defined in collections.js and import them into our config file. You’d now do exactly the same for your filters, transforms, shortcodes, etc.

If you want to see this method in action, visit the public repository of Ryan Mulligan’s personal site.

Very tidy!

This approach has the advantage of producing a really compact config file. There is something I don’t like about this method though.

We have brought structure into it, but I also want to see what’s being used in my project, right there, in my config file. I want to see which collections I’m defining, which filters, and so on. So here comes method two!

Method 2: named exports

Instead of collections.js create another folder inside config called collections, and in there you put a file called index.js:

// blog posts
const getPosts = collection => {
return collection.getFilteredByGlob('src/posts/**/*.md');
};

// projects
const getProjects = collection => {
return collection.getFilteredByGlob('src/projects/**/*.md');
};

module.exports = {
getPosts,
getProjects
};

Named exports can be exported individually, or batched together and exported at the bottom of the module. Exporting everything at the bottom, like in the example here, is so much cleaner, so I naturally favor this method.

And inside your eleventy.js:

// Importing from config
const {getPosts, getProjects} = require('./config/collections/index.js');

module.exports = eleventyConfig => {
// Collections
eleventyConfig.addCollection('posts', getPosts);
eleventyConfig.addCollection('projects', getProjects);

return {
dir: {
input: 'src',
output: 'dist',
includes: '_includes',
layouts: '_layouts'
}
};
};

Done!

Everything is neat and I can see at a glance what I am importing for this project.

If there are too many filters, collections or shortcodes, I subdivide them further into their own folders, for example only the filters for handling the date in a common place. Larger blocks like the ones for the Eleventy Image shortcodes get their very own folder.
The exported values are then imported into the parent index.js, and then exported all together again for the eleventy.js file. 🤪

Method 3: adding another config file as a plugin

After I shared this article on Mastodon, Zach pointed out that there is yet another way to outsource my config components:

eleventyConfig.addPlugin(require('other-config-file.js'));

Not only is this the most compact notation, as I don’t have to import my return values first, but I also don’t have to remodel any code, as these outsourced config-files work just the same as eleventy.js, returning a callback function. And I can see what I am importing!

I illustrate this with the example of an html minification using Eleventy’s built-in addTransform.

In your eleventy.js:

// nothing to import! :)

module.exports = eleventyConfig => {
// Transforms
eleventyConfig.addPlugin(require('./config/transforms/html-config.js'));

return {
dir: {
input: 'src',
output: 'dist',
includes: '_includes',
layouts: '_layouts'
}
};
};

Your html-config.js:

const htmlmin = require('html-minifier-terser');
const isProduction = process.env.ELEVENTY_ENV === 'production';

module.exports = eleventyConfig => {
eleventyConfig.addTransform('html-minify', (content, path) => {
if (path && path.endsWith('.html') && isProduction) {
return htmlmin.minify(content, {
collapseBooleanAttributes: true,
collapseWhitespace: true,
decodeEntities: true,
includeAutoGeneratedTags: false,
removeComments: true
});
}

return content;
});
};

Excellent!

Next up: Passthrough File Copy.

Structuring your Passthrough File Copies

Sometimes we just want to copy files over to our output folder, without subjecting them to further transformation processes. Exactly as they are. This is how Passthrough File Copies come into play.

Keep directory structure intact

Let’s say you have stored your local fonts in src/assets/fonts.

If you want to keep the same nesting structure, you add the following to eleventy.js:

module.exports = eleventyConfig => {
// Passthrough Copy
eleventyConfig.addPassthroughCopy('src/assets/fonts/');

return {
dir: {
input: 'src',
output: 'dist',
includes: '_includes',
layouts: '_layouts'
}
};
};

Now your fonts will be copied over with the same directory structure, in dist/assets/fonts/.

I usually have more than one folder in assets that I want to deal with in the same way.
There is a concise way for this too!

['src/assets/fonts/', 'src/assets/images/', 'src/assets/pdf/'].forEach(path =>
eleventyConfig.addPassthroughCopy(path)
);

We put all directories into an array and apply the forEach() method to execute the passthrough once for each array element.

Copy the files to another directory

Maybe you want to copy your files to another directory. For me, this makes especially sense for my favicon variants. You can tell the browser to look for them inside a folder, but my experience has been that they’re best put in the root directory of the web page. However, I don’t want to see them in my project root (too much noise!), so I usually put them all in src/assets/images/favicon/.

To copy a single file over to the dist root directory, use this snippet:

eleventyConfig.addPassthroughCopy({
'src/assets/images/favicon/apple-touch-icon.png': 'apple-touch-icon.png'
});

Now you could do this for every favicon file, but that would be a lot of unnecessary repetition. Instead, you can select all the files in the favicon directory with the * (asterisk) wildcard:

eleventyConfig.addPassthroughCopy({
'src/assets/images/favicon/*': '/'
});

By the way, regarding favicons, I recommend reading Andrey Sitnik’s article about which variants you really need.

Wrap up

This is how I’m currenlty structuring my projects. You can see this being applied in my starter eleventy-excellent. A wonderful example of a tidy Eleventy config file can be found in the repository of Miriam Suzanne’s personal website.

https://github.com/mirisuzanne/mia/blob/main/.eleventy.js

Generally, it is always a great idea to dive deeply into the repositories of starter projects or personal sites of other developers.

There are so many great ideas out there!


I try to keep my articles up to date. If you see something that is not true (anymore), or something that should be mentioned, feel free to edit the article on GitHub.

Webmentions

2 Mentions/Responses

20 Replies

Eleventy 🎈 v2.0.0-canary.18 Eleventy 🎈 v2.0.0-canary.18

@lene great advice! I did want to also mention that you can use `eleventyConfig.addPlugin(require("other-config-file.js"))` and then `other-config-file.js` can just have more config like `module exports = function(eleventyConfig)` source

Lene Saile Lene Saile

@eleventy Oh, nice, I didn't know that! I'll try it out and add it to the article! source

mortendk 🤘 mortendk 🤘

@lene now theres a post i need to read :) source

Lene Saile Lene Saile

@mortendk Wait, I'm about to extend it a little bit after Zach's input! :) source

vince ⚡ vince ⚡

@lene @eleventy oooooh, i can't wait to dig into this--thank you for sharing! source

Benny Powers 🇨🇦️🇮🇱️ Benny Powers 🇨🇦️🇮🇱️

@lene @eleventy how do you do your comments? Looks great source

Sia Karamalegos Sia Karamalegos

@lene Nice! I'm also a big fan of putting my filters and shortcodes in a separate file to tidy it up some more - which I may have learned from @mxbck https://github.com/siakaramalegos/sia.codes-eleventy/blob/main/.eleventy.js#L9-L12 sia.codes-eleventy/.eleventy.js at main · siakaramalegos/sia.codes-... source

Lene Saile Lene Saile

@bp Do you mean the #webmentions? @mxbck and @sia both have great articles on this, I'm linking Sias here: https://sia.codes/posts/webmentions-eleventy-in-depth/ webmentions An In-Depth Tutorial of Webmentions + Eleventy source

Bernard Nijenhuis Bernard Nijenhuis

@lene @mxbck Were they sorted right before merging them? If not, maybe you need the sorting parameters? "&sort-by=published&sort-dir=up" source

Nicolas Hoizey Nicolas Hoizey

@lene my own version is based on the one from @sia but it looks like I added a sort: https://github.com/nhoizey/nicolas-hoizey.com/blob/main/_scripts/update-webmention.js#L95-L99@mxbck nicolas-hoizey.com/update-webmention.js at main · nhoizey/nicolas-hoizey.com source

Lene Saile Lene Saile

@nhoizey Can it be possible that I'm not sorting at all? I'm going to check that again. source

Lene Saile Lene Saile

@bnijenhuis I'll try that, thank you! source

Lene Saile Lene Saile

@nhoizey @bnijenhuis It seems like I *sorted it out*, hehe. I guess I really wasn't sorting anything to start with. Thank you for your help! source

Bernard Nijenhuis Bernard Nijenhuis

@lene Glad to hear you fixed it! source

Max Böck Max Böck

@lene they're ordered in the API, but I also have odering built into the filter that does the aggregation for each URLhttps://github.com/maxboeck/mxb/blob/b4e4fb460be0a11cff1656734edf28238be54007/utils/filters.js#L76 mxb/filters.js at b4e4fb460be0a11cff1656734edf28238be54007 · maxboeck/mxb source

Lene Saile Lene Saile

@mxbck Thank you Max! I'll check the whole code again tomorrow, it's working, but I'm sure something is redundant as I have now mixed in a little bit from @nhoizey's code. source

Max Böck Max Böck

@lene @nhoizey that's always a good idea ;) love your site btw! source

Lene Saile Lene Saile

@mxbck thank you!! I'm a big fan of your site as well! source

Nicolas Hoizey Nicolas Hoizey

@lene @mxbck maybe we could find a way to share the code in a plugin, so we don't copy/paste, and mix, too much… 😅 source

Nicolas Hoizey Nicolas Hoizey

@lene you're welcome! 🙏As @mxbck said, it should be sorted when coming from the API, so I don't know why I did add this sort… 😅@bnijenhuis source

Have you published a response? Let me know where: