Using Liquid Blocks in Eleventy Layouts

Using Liquid Blocks in Eleventy Layouts

Today's post is based on an interesting question I ran into on StackOverflow: How do I use LiquidJS Layout Blocks in Eleventy? The person asking the question was trying to accomplish the following:

Eleventy makes it super easy to use templates with your work. You create a file, add front matter, and specify a layout file:

---
title: Hello World
layout: main
---

You can then create a file, _includes/main.liqdui, and then render your content like so:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<title></title>
	<style>
	main {
		padding: 30px;
		background-color: #c0c0c0;
	}
	</style>
</head>
<body>

<main>
{{ content }}
</main>

</body>
</html>

Layouts work great when your content lands in the middle of some block of HTML. But consider a layout that has a block that needs to be dynamic and that's not between a pair of tags?

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<title></title>
	<style>
	main {
		padding: 30px;
		background-color: #c0c0c0;
	}

	#footer {
		margin-top: 10px;
	}
	</style>
</head>
<body>

<main>
{{ content }}
</main>

<footer>
	This is the footer.
</footer>

</body>
</html>

In the example above, we've added a footer element that right now is hard coded, but we'd like to have our page templates pass in content. So for example:

<footer>
{{footer}}
</footer>

So, one quick solution is to just use front matter!

---
title: Hello World
layout: main
footer: This is the footer!
---

That works perfectly fine, but really only works for short blocks of static content. If I wanted something more dynamic, I'd be out of luck. Computed Data could be a solution, but the StackOverflow user was looking to use a Liquid feature, Blocks. Idealy, this is what they would like to do:

---
title: Hello World
layout: main
---

## Hello World!

This is me testing.

{% block footer %}
This will be used in the footer.
{% endblock %}

The result is not what you would expect:

The web page rendered the footer in the main content

Ok, so what now? I did some Googling and found this Eleventy issue: Using Nunjucks blocks from within Markdown. In the issue, they described that in order to do this with Nunjucks, you can't use Eleventy layouts, but must specify the layout with Nunjucks itself.

Alright, so I tried that in test1.md:

---
title: Test 1
---

{% layout main %}

## Hello World!

This is me testing.

{% block footer %}
This will be used in the footer.
{% endblock %}

And... it kinda worked. But I noticed a few issues. First, I lost all my output. It looks like when Liquid executed in Markdown, it ended up folowing one of the Markdown rules where code that's tabbed over is meant to be used as displayed source code, so it escaped it. I fixed that by removing the tabs in my layout. Annoying, but I can deal. Then, to display my footer, I had to change from:

{{ footer }}

To:

{% block footer %}{% endblock %}

I'm ok with that change as it lets me specify a default value for footer as well. Now I've got this:

Footer displayed, not the content

And if you think about it, the missing "main" content makes sense. I'm no longer using Eleventy to do my layout, so in layout.liquid, {{ content }} doesn't exist. (Well the code is there of course, I mean the value of content isn't set.)

So how do we fix this now? Use blocks again. Here's layout.liquid now:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
<style>
main {
	padding: 30px;
	background-color: #c0c0c0;
}

#footer {
	margin-top: 10px;
}
</style>
</head>
<body>

<main>
{% block content %}{% endblock %}
</main>

<footer>
{% block footer %}{% endblock %}
</footer>

</body>
</html>

And then back in test1.md:

---
title: Test 1
---

{% layout main %}

{% block content %}
## Hello World!

This is me testing.
{% endblock %}

{% block footer %}
This will be used in the footer.
{% endblock %}

Woot! Now it's perfect! Except my other site pages don't work!

Main content is gone

On a page using Eleventy's built in layout processing, my content isn't working. Dangit. Luckily there's an easy, if somewhat hackish, solution:

<main>
{% block content %}{% endblock %}
{{ content }}
</main>

On pages not using the footer block, the content variable will be declared. On pages using it, the content block will exist.

Whew.

So.... this works, and honestly it isn't too terribly ugly. But then I remembered something. A few weeks ago I blogged about creating "additive" capture blocks in Eleventy. Basically, I wanted to wrap content two or more times and have it append to one variable. I'd then display that variable.

In that blog post, I created two shortcodes, one called mycapture and one called displaycapture. My code worked by storing the values so that I could add to it and then display it. Today I discovered a bug in that implementation (fixed in this post and will be fixed in the old post by the time you read this) but was able to quickly correct it. So here's my .eleventy.js with my shortcodes:

module.exports = function(eleventyConfig) {

	let _CAPTURES;
	eleventyConfig.on('beforeBuild', () => {
		//I need this to wipe _CAPTURES when editing pages, wouldn't be an issue in prod
		_CAPTURES = {};
	});
	
	
	eleventyConfig.addPairedShortcode("mycapture", function (content, name) {
		if(!_CAPTURES[this.page.inputPath]) _CAPTURES[this.page.inputPath] = {};
		if(!_CAPTURES[this.page.inputPath][name]) _CAPTURES[this.page.inputPath][name] = '';
		_CAPTURES[this.page.inputPath][name] += content;
		return '';
	});
	
	eleventyConfig.addShortcode("displaycapture", function(name) {
		if(_CAPTURES[this.page.inputPath] && _CAPTURES[this.page.inputPath][name]) return _CAPTURES[this.page.inputPath][name];
		return '';
	});

};

So while we don't need additive shortcodes, we can now do this (in test2.md):

---
title: Test 2
layout: main2
---

## Second Test

This is me testing more.

{% mycapture "footer" %}
This is my footer!
{% endmycapture %}

You'll notice I'm using main2.liquid for the layout. That's closer to my original version, but uses my shortcode for the footer:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<title></title>
	<style>
	main {
		padding: 30px;
		background-color: #c0c0c0;
	}

	#footer {
		margin-top: 10px;
	}
	</style>
</head>
<body>

	<main>
	{{ content }}
	</main>

	<footer>
	{% displaycapture "footer" %}
	</footer>

</body>
</html>

I like this solution as it removes some of the complexity around the blocks and lets me keep using "normal" Liquid layouts. Anyway, as always, I'd love to hear what you think. You can find this solution here: https://github.com/cfjedimaster/eleventy-demos/tree/master/blockquestion

Photo by Michael Fousert on Unsplash

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com