EnglishNederlandsFrançais Toggle theme

Eleventy Baseline

Start building your site, skip the recurring setup work.
Table of Contents

Build a multilingual Baseline site

Add a second language to a Baseline site, with everything you'd expect: per-language URLs, hreflang on every page, a sitemap for each language plus an index. Multilingual is opt-in. Most projects don't need it; if yours does, this chapter is the full setup.

By the end you'll have an English and Dutch site sharing a single Eleventy build, with Baseline doing the hreflang and sitemap wiring on its own.

What you will build

  • Pages in English and Dutch sharing one Eleventy build.
  • Baseline configured with multilingual on, plus defaultLanguage and languages set in _data/settings.js.
  • Hreflang links (the <link rel="alternate" hreflang="..."> tags that point search engines at a page's translations) written automatically, and a per-language sitemap for each language.

How activation works

Multilingual mode requires three things, all present:

  • options.multilingual: true passed to baseline() in eleventy.config.js.
  • defaultLanguage set in _data/settings.js.
  • A non-empty languages map in _data/settings.js.

Setting defaultLanguage and languages is the site's identity. Turning multilingual on is the runtime decision. Both halves are needed; Baseline won't infer one from the other.

Prerequisites

  • Node 20.15.0 (or >=20) and npm.
  • package.json with "type": "module" and the dev/build scripts from the simple site tutorial:
    {
    	"name": "simple-baseline-site",
    	"type": "module",
    	"scripts": {
    		"start": "rimraf dist/ && npx @11ty/eleventy --serve",
    		"build": "rimraf dist/ && cross-env ELEVENTY_ENV=production npx @11ty/eleventy"
    	}
    }

1) Install required packages

npm install @11ty/eleventy @11ty/eleventy-img # Eleventy and 11ty Image
npm install rimraf cross-env # Helper packages
npm install @apleasantview/eleventy-plugin-baseline # Finally, install Baseline

2) Configure Eleventy with multilingual on

Create eleventy.config.js:

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

/** @param {import("@11ty/eleventy").UserConfig} eleventyConfig */
export default async function (eleventyConfig) {
	eleventyConfig.addPlugin(
		baseline(settings, {
			multilingual: true,
			head: {
				titleSeparator: ' | ',
				showGenerator: true
			}
		})
	);
}

export const config = baselineConfig;

Create a local .env so canonical URLs, hreflang, and sitemaps use a real origin during development. In production, set URL in your hosting environment so the same outputs reach for the live domain instead.

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

Baseline's multilingual support is a thin layer over Eleventy's built-in i18n plugin. The Eleventy i18n docs cover the mechanics underneath.

3) Site data and languages

Open src/_data/settings.js from the previous tutorials and add defaultLanguage and the languages map. Everything else (title, url, head extras) carries forward; the merged file looks like this:

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

	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'
		}
	},

	head: {
		link: [{ rel: 'stylesheet', href: '/assets/css/index.css' }],
		script: [{ src: '/assets/js/index.js', defer: true }],
		meta: [{ name: 'color-scheme', content: 'light dark' }]
	}
};

Each language's contentDir points at its per-language source folder under src/. Baseline reads the map to know which languages exist, and uses defaultLanguage to mark the canonical one.

4) Per-language directory data

Tell Eleventy which language each content folder belongs to. Baseline reads lang on each page to group translations and emit hreflang.

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

export default { lang: 'en' };

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

export default { lang: 'nl' };

5) Minimal layout

Create src/_includes/layouts/base.njk. The <baseline-head> placeholder is what Baseline replaces with the rendered <head> at build time. With multilingual on and a translationKey on each page (next step), the hreflang links go in automatically. No for loop in your layout.

Refer to the minimal layout step in the Build a simple Baseline site tutorial.

6) Add localized pages

A translationKey is the string that ties translations of the same page together. Pages with the same key across languages are recognised as translations of one another, and that's what lets Baseline emit the right hreflang.

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

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

Welcome to the English home page.

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

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

Welkom op de Nederlandse homepagina.

Delete the permalink field from src/content/pages/index.md.

7) Minimal assets (reuse from prior tutorial)

The asset side is unchanged, so reuse what you already have from the Build a simple Baseline site tutorial:

  • src/assets/css/index.css with the same minimal styles as before.
  • src/assets/js/index.js, optionally a small script (a DOMContentLoaded log is plenty).

8) Hreflang

Hreflang is the set of <link rel="alternate" hreflang="..."> tags that point search engines at a page's translations. Baseline emits them automatically once two things are in place:

  1. Multilingual mode is on (options.multilingual: true plus defaultLanguage and languages in settings).
  2. The page has a translationKey in its front matter, matched by its translations.

With both pages in place from the previous step and multilingual on, the rendered <head> for the homepage will contain:

<link rel="alternate" hreflang="en" href="http://localhost:8080/" />
<link rel="alternate" hreflang="nl" href="http://localhost:8080/nl/" />
<link rel="alternate" hreflang="x-default" href="http://localhost:8080/" />

9) Run the site locally

npm start
  • Visit /en/ for English and /nl/ for Dutch.
  • View page source on either side. You should see <link rel="alternate" hreflang="en" href="...">, <link rel="alternate" hreflang="nl" href="...">, and the x-default entry. Baseline emits all three because both pages share translationKey: 'homepage' and multilingual is on.
  • Dev writes to dist/ while it runs, so the generated sitemap.xml plus per-language sitemaps (dist/en/sitemap.xml, dist/nl/sitemap.xml) are inspectable while the server is up.

10) Production build and inspect output

Change URL in .env to an absolute URL. On a deployed site that's your real production URL; for this exercise, any absolute URL (e.g. https://www.example.com/) works to verify the output. Then run:

npm run build
  • Check dist/ for per-language output.
  • dist/sitemap.xml is now the sitemap index, with the per-language sitemaps (dist/en/sitemap.xml, dist/nl/sitemap.xml) listed inside.
  • Reset URL in .env to "http://localhost:8080/".

Next steps

  • Add more pages under src/content/en/ and src/content/nl/, matching translationKey values so the translations stay linked.
  • Localise labels (nav text and similar) through data files keyed by lang.
  • Set pathPrefix if you deploy under a subpath, and keep settings.url pointing at the production origin so canonicals and sitemap links match.
  • The multilang module reference has the full activation rules and helpers; the filters reference covers the translation-aware filters.