Eleventy Plugin Baseline

A magic carpet ride

Table of Contents

Build a Multilingual Baseline Site

Create a two-language Eleventy site (en + nl) using @apleasantview/eleventy-plugin-baseline. We’ll enable Baseline’s multilingual support, add localized pages, render hreflang/alternate links, and confirm per-language sitemaps. Uses npm, Node 20.15.0, and the same minimal layout style as the previous tutorial.

What you’ll build

  • A basic site with English and Dutch pages.
  • Baseline configured with multilingual: true and language metadata.
  • Layout with hreflang/alternate links; automatic per-language sitemaps.

Prerequisites

  • Node 20.15.0 (or >=20) and npm.
  • package.json with "type": "module".
  • Minimal scripts (same as prior tutorial):
    {
    	"type": "module",
    	"scripts": {
    		"dev": "npx @11ty/eleventy --serve",
    		"build": "npx @11ty/eleventy"
    	}
    }

1) Install Eleventy, Eleventy Image and Baseline

npm install @11ty/eleventy @apleasantview/eleventy-plugin-baseline @11ty/eleventy-img

2) Configure Eleventy with Baseline multilingual

Create eleventy.config.js:

import baseline, { config as baselineConfig } from '@apleasantview/eleventy-plugin-baseline';
import i18n from './src/_data/i18n.js';

/** @param {import("@11ty/eleventy").UserConfig} eleventyConfig */
export default async function (eleventyConfig) {
	// Multilingual on; default language and languages pulled from i18n data.
	eleventyConfig.addPlugin(
		baseline({
			multilingual: true,
			defaultLanguage: i18n.defaultLanguage,
			languages: i18n.languages
		})
	);
}

export const config = baselineConfig;

Baseline’s multilang support uses Eleventy’s built-in i18n plugin. For plugin details, see the Eleventy i18n docs: https://www.11ty.dev/docs/plugins/i18n/.

Baseline will configure HtmlBasePlugin to use process.env.URL automatically; create a local .env for dev, and set URL in your host/CI for production so canonicals, hreflang, and sitemaps use the right origin.

Create a local .env now:

ELEVENTY_ENV="development"
URL="http://localhost:8080/"

3) Required site data

src/_data/site.js:

export default {
	title: 'Multilingual Baseline Site',
	tagline: 'Hello, Eleventy + Baseline (i18n)',
	url: process.env.URL || 'http://localhost:8080/',
	defaultLanguage: 'en',
	noindex: false
};

src/_data/head.js:

export default {
	link: [{ rel: 'stylesheet', href: '/assets/css/index.css' }],
	script: [{ src: '/assets/js/index.js', defer: true }]
};

4) Define languages

Create src/_data/i18n.js:

export default {
	defaultLanguage: 'en',
	languages: {
		en: {
			contentDir: 'content/en/',
			languageCode: 'en',
			languageName: 'English',
			title: 'Baseline (EN)',
			tagline: 'Hello'
		},
		nl: {
			contentDir: 'content/nl/',
			languageCode: 'nl',
			languageName: 'Nederlands',
			title: 'Baseline (NL)',
			tagline: 'Hallo'
		}
	}
};

5) Per-language directory data

src/content/en/en.11tydata.js:

export default { lang: 'en' };

src/content/nl/nl.11tydata.js:

export default { lang: 'nl' };

6) Minimal layout with hreflang

Use the same simple layout as before, plus alternates. Create src/_includes/layouts/base.njk:

<!DOCTYPE html>
<html lang="{{ lang | default(site.defaultLanguage) }}">
  <head>
    <baseline-head></baseline-head>
    {# hreflang/alternate links from collections.translationsMap #}
    {% set translations = collections.translationsMap[translationKey] %}
    {% if translations %}
      {% for language, item in translations %}
        <link rel="alternate" hreflang="{{ item.lang }}" href="{{ item.url }}">
        {% if item.isDefaultLang %}<link rel="alternate" hreflang="x-default" href="{{ item.url }}">{% endif %}
      {% endfor %}
    {% endif %}
    <meta name="color-scheme" content="light dark">
  </head>
  <body>
    <main id="main">
      <h1>{{ title }}</h1>
      {{ content | safe }}
      {% if page.url !== "/" %}
        <p><a id="go-back" href=""><span style="vertical-align: text-bottom;">&#8592;</span>&nbsp;Go back</a></p>
      {% endif %}
    </main>
    <script>
    const backBtn = document.getElementById("go-back");
    if ( backBtn ) {
      backBtn.addEventListener("click", (event) => {
        event.preventDefault();
        history.back();
      });
    }
    </script>
  </body>
</html>

7) Add localized pages

src/content/en/pages/index.md:

---
title: 'Hello Baseline (EN)'
description: 'English home'
permalink: '/en/'
layout: 'layouts/base.njk'
translationKey: 'homepage'
---

Welcome to the English home page.

src/content/nl/pages/index.md:

---
title: 'Hallo Baseline (NL)'
description: 'Nederlandse home'
permalink: '/nl/'
layout: 'layouts/base.njk'
translationKey: 'homepage'
---

Welkom op de Nederlandse homepagina.

8) Minimal assets (reuse from prior tutorial)

  • src/assets/css/index.css — same minimal styles as before.
  • src/assets/js/index.js — optional small script (e.g., DOMContentLoaded log).

9) Run the site locally

npx rimraf dist/ && npm run dev
  • Visit /en/ for English and /nl/ for Dutch.
  • View page source to see <link rel="alternate" hreflang="..."> entries.
  • While dev runs, Eleventy also writes to dist/, so you can peek at the generated files including sitemap.xml and per-language sitemaps like dist/en/sitemap.xml and dist/nl/sitemap.xml.

10) Production build and inspect output

npx rimraf dist/ && npm run build
  • Check dist/ for per-language output.
  • dist/sitemap.xml plus per-language sitemaps (e.g., dist/en/sitemap.xml, dist/nl/sitemap.xml) if multiple languages are present.

11) Next steps

  • Add more pages under src/content/en/ and src/content/nl/ with matching translationKey values to link translations.
  • Localize labels (e.g., nav text) via data files keyed by lang.
  • Set pathPrefix if deploying under a subpath; ensure site.url is set to your production URL for correct canonicals and sitemap links.