Build Awesome Power tools by Eleventy Blades

Table of Contents

Base eleventy.config.js

The package includes a fully-configured Eleventy config file eleventy.config.js that you can symlink to your project to get:

Benefits of symlinking:

Installation as simple as:

npm install @anyblades/eleventy-blades
ln -s ./node_modules/@anyblades/eleventy-blades/src/eleventy.config.js
How it works

/* Plugins */
import { RenderPlugin } from "@11ty/eleventy";
import eleventyBladesPlugin from "@anyblades/eleventy-blades";
/* Dynamic plugins */
let eleventyNavigationPlugin;
try {
  eleventyNavigationPlugin = (await import("@11ty/eleventy-navigation")).default;
} catch (e) {
  // @11ty/eleventy-navigation not installed
}
let pluginTOC;
try {
  pluginTOC = (await import("@uncenter/eleventy-plugin-toc")).default;
} catch (e) {
  // @uncenter/eleventy-plugin-toc not installed
}
let feedPlugin;
try {
  feedPlugin = (await import("@11ty/eleventy-plugin-rss")).feedPlugin;
} catch (e) {
  // @11ty/eleventy-plugin-rss not installed
}
/* Libraries */
import markdownIt from "markdown-it";
/* Dynamic libraries */
let slugify;
try {
  slugify = (await import("@sindresorhus/slugify")).default;
} catch (e) {
  // @sindresorhus/slugify not installed
}
let markdownItAnchor;
try {
  markdownItAnchor = (await import("markdown-it-anchor")).default;
} catch (e) {
  // markdown-it-anchor not installed
}
let markdownItAttrs;
try {
  markdownItAttrs = (await import("markdown-it-attrs")).default;
} catch (e) {
  // markdown-it-attrs not installed
}
/* Data */
import yaml from "js-yaml";
/* System */
import fs from "node:fs";
import path from "node:path";

/**
 * Eleventy Configuration
 * @param {Object} eleventyConfig - The Eleventy configuration object
 * @returns {Object} The Eleventy configuration object
 */
export default function (eleventyConfig) {
  const inputDir = eleventyConfig.directories.input;

  /* Jekyll parity */
  eleventyConfig.addPassthroughCopy("assets");
  eleventyConfig.addGlobalData("layout", "default");
  eleventyConfig.setLiquidOptions({
    dynamicPartials: false, // allows unquoted Jekyll-style includes
    root: [
      eleventyConfig.directories.includes,
      inputDir + "../_includes", // for shared multisite includes
      fs.realpathSync(path.resolve("./node_modules/@anyblades/blades/_includes")), // for symlinks to work after https://github.com/harttle/liquidjs/pull/870
    ],
  });
  eleventyConfig.addFilter("relative_url", (content) => content); // dummy

  /* Plugins */
  eleventyConfig.addPlugin(RenderPlugin);
  if (eleventyNavigationPlugin) eleventyConfig.addPlugin(eleventyNavigationPlugin);
  eleventyConfig.addPlugin(eleventyBladesPlugin, {
    mdAutoNl2br: true,
    mdAutoUncommentAttrs: true,
    mdAutoRawTags: true,
    autoLinkFavicons: true,
    siteData: true,
    filters: [
      "attr_set",
      "attr_includes",
      "merge",
      "remove_tag",
      "if",
      "attr_concat",
      "fetch",
      "section",
      "strip_tag",
      "unindent",
      "date",
    ],
  });
  if (pluginTOC) {
    eleventyConfig.addPlugin(pluginTOC, {
      ignoredElements: [".header-anchor", "sub"],
      ul: true,
      wrapper: (toc) => `${toc}`,
    });
  }
  // https://www.11ty.dev/docs/plugins/rss/#virtual-template
  if (feedPlugin) {
    eleventyConfig.addCollection("feed", (collectionApi) => collectionApi.getAll().filter((item) => item.data.revised));
    let siteData = {};
    try {
      siteData = yaml.load(fs.readFileSync(`${inputDir}/_data/site.yml`, "utf8"));
    } catch (e) {
      // _data/site.yml not found
    }
    eleventyConfig.addPlugin(feedPlugin, {
      type: "atom", // or "rss", "json"
      outputPath: "/feed.xml",
      collection: {
        name: "feed", // iterate over `collections.posts`
        limit: 100, // 0 means no limit
      },
      metadata: siteData,
    });
  }

  /* Libraries */
  let md = markdownIt({
    html: true,
    linkify: true,
  });
  if (markdownItAnchor) {
    md = md.use(markdownItAnchor, {
      slugify: slugify, // @TODO: TRICKS
      permalink: markdownItAnchor.permalink.ariaHidden(),
    });
  }
  if (markdownItAttrs) md = md.use(markdownItAttrs);
  eleventyConfig.setLibrary("md", md);
  eleventyConfig.addFilter("markdownify", (content) => md.render(String(content ?? "")));

  /* Data */
  eleventyConfig.addDataExtension("yml", (contents) => yaml.load(contents));

  /* Build */
  eleventyConfig.addPassthroughCopy(
    {
      _public: ".",
      ...(inputDir !== "." && { [`${inputDir}/_public`]: "." }),
    },
    { expand: true }, // This follows/resolves symbolic links
  );

  /* Dev tools */
  // Follow symlinks in Chokidar used by 11ty to watch files
  eleventyConfig.setChokidarConfig({ followSymlinks: true });
}

Base 11ty npm scripts via npm workspace

This package provides a pre-configured do folder setup that helps organize your development workflow using npm workspaces. The do folder contains scripts for building and running your Eleventy project.

Installation:

  1. Install /anyblades/eleventy-blades to reuse pre-defined 11ty scripts from there:
npm install @anyblades/eleventy-blades
  1. Create a helper folder do to symlink the do/package.json within:
mkdir do
cd ./do
ln -s ../node_modules/@anyblades/eleventy-blades/src/do/package.json
  1. Finally register do folder as npm workspace in your root package.json:
{
  ...
  "workspaces": ["do"],
  "scripts": {
    "start": "npm -w do run start",
    "stage": "npm -w do run stage",
    "build": "npm -w do run build"
  },
  ...
}

Done! 🎉 Now you can run:

Living examples:

Benefits:


Data helpers

Adds global site data to your Eleventy project, providing commonly needed values that can be accessed in all templates:

Variable Value
{{ site.year }} The current year as a number (e.g., 2026)
{{ site.prod }} Boolean indicating if running in production mode (true for eleventy build, false for eleventy serve)
How it works
/**
 * Add site.year and site.prod global data
 * - site.prod: Boolean indicating if running in production mode (build) vs development (serve)
 * - site.year: Sets the current year to be available in all templates as {{ site.year }}
 *
 * @param {Object} eleventyConfig - The Eleventy configuration object
 */
export function siteData(eleventyConfig) {
  eleventyConfig.addGlobalData("site.prod", () => process.env.ELEVENTY_RUN_MODE === "build");
  eleventyConfig.addGlobalData("site.year", () => new Date().getFullYear());
}

More

Find and kill 11ty processes

ps aux | grep eleventy
pkill -f eleventy

You can even combine it with other processes hanging around:

ps aux | grep -E 'eleventy|tailwind|.bin/serve'
pkill -f tailwind
pkill -f .bin/serve

Forms

Minform starter

Minform v0.3.1 released at /johnheenan/minform which will work with formsubmit.io above to convert forms to email. This is the Hostfurl independent version of Minformhf.

To work with formsubmit.io the following edits are required in _data/site.yml:

nohtmx: true
corsprod: true
corsurl: https://formsubmit.co
formpath: /somearbitrarycharacters

Demo at minform.hostfurl.com

/channels/741017160297611315/1494619630273298504/1495806652396601444

Cloudflare Workers, D1, and Turnstile

I recently implemented forms for recipe reviews and membership signup using Cloudflare Workers, D1, and Turnstile.

Here's an example: /recipes/sweet-potato-apple-cake/#new-review

Turnstile invisibly protects form submissions against abuse. Cloudflare Workers process them and store them into D1.

It costs pennies and so far works perfectly.

/channels/741017160297611315/1494619630273298504/1495698228417658942


See also:

Eleventy Blades plugin

Ultimate blade kit for 11ty (Build Awesome).

View more

Minimal yet long-living starters that support continuous updates and evolve with your project
based on Eleventy Blades plugin

Let's try to make a shift from a commodity (a template) to a service (long-term stability), and solve the "maintenance trap" where developers spend more time fixing their build tools than writing content.

View more