Menu Search
Jump to the content X X
Smashing Conf Barcelona 2016

We use ad-blockers as well, you know. We gotta keep those servers running though. Did you know that we publish useful books and run friendly conferences — crafted for pros like yourself? E.g. upcoming SmashingConf Barcelona, dedicated to smart front-end techniques and design patterns.

Content Modeling With Jekyll

It’s not exactly a new subject, but lately I’ve had reason to revisit the skill of content modeling in my team’s work. Our experience has reached a point where the limitations of how we practice are starting to become clear. Our most common issue is that people tend to tie themselves and their mental models to a chosen platform and its conventions.

Instead of teaching people how to model content, we end up teaching them how to model content in Drupal, or how to model content in WordPress. But I’d prefer that we approach it from a focus on the best interests of users, regardless of which platform said content will end up in.

This line of thought brought me back to an idea I’ve become a bit obsessed with, which is that when we have to create an artifact to communicate certain ideas to a client, the process almost always goes better when that artifact is as close as possible to an actual website instead of a picture of a website or a PDF full of diagrams.

Thus the question I ended up asking myself: was there a tool I could use to help people quickly model content in a platform-agnostic manner and simultaneously build an artifact that was ideal for communicating intent to a client or team?

A High-Level Theory Of Content Modeling Link

Let’s divert a bit before we get into Jekyll. I believe you can remove all the conventions and platform-specific language from the content modeling discussion and define it as a three part system:

  1. The core idea is that of an object: some unit of content that holds together across a site. For example, a blog post or a person would be an object on a site.
  2. Objects have attributes that define them. A blog post could have a title, a body of content, an author. A person could have a name, a photo, a bio.
  3. Objects have relationships that determine where they end up on a site, and layouts have logic that defines which attributes of an object are used and where. Our example blog post object is connected to a person object because its author is a person. We output the author’s name and a link to their profile on the post page, and we output their full bio on their profile page.

I wanted to create a system that respected the high-level ideas I’ve outlined, but allowed the team the freedom to create attributes and relationships as they saw fit without worrying about ideas specific to certain platforms. Instead, they could focus on defining content based on what’s best for users. And it turns out that Jekyll has the features to make this possible.

Enter Jekyll Link

Jekyll1 is a static blogging framework. And before you head for the comment section, yes, I am aware that it is correct to regard it as a platform in its own right. However, it has a few advantages over something like Drupal or WordPress.

Jekyll takes simplicity seriously. It doesn’t have a database, instead relying on flat files and some Liquid2 templating tags that generate plain old HTML. Liquid is limited, simple and extremely human-readable. I’ve found that I can show somebody a template constructed with some Liquid tags and as long as they have a bit of experience with front-end code, they understand what the template is doing.

What’s nice about this is that we don’t have to show somebody how to get a database running, how to hook their templates up to it, how to configure the admin area of their CMS to work with their templates, and so on. Instead, we can install Jekyll and teach how to start a server. If the user is on a Mac there is an excellent chance that this is a two minute process that just works the first time we try it.

Jekyll also doesn’t force a lot of conventions down the user’s throat. You have the freedom to create your preferred file structure and asset pipeline, establish your own relationships between files, and write markup in the way you like best. What few conventions it possesses are easily reconfigured to suit your style.

Using Collections To Create And Contain Objects Link

While it’s still considered an experimental feature, Jekyll has something called collections3 that will allow us to create the system I’m describing.

Basically, you create a folder and name it after the type of object you’re creating. Then you add files to that folder, and each file represents an object in that collection. Once you have objects, you can create attributes for them using YAML4 at the beginning of every file. YAML is a syntax that allows you to define key/value pairs that easily store information.

What’s nice about this system is how incredibly simple it is. Everything is human-readable and functions in a manner that’s easy for a new user to learn. Rather than creating a lot of documentation on how somebody should create content and relationships in the final system, you can just create it. Designers can see what the objects and their attributes are so they can plan their design system. Front-end developers have a functioning website to architect their markup and CSS with.

Because they aren’t forced to use a specific system or convention, they can just use the one they prefer or the conventions of the final platform for the project. And back-end developers can easily determine the intention of the designer when transferring over templates and logic into whatever CMS they decide to use because it’s already written out for them.

Let’s Build A Simple Site With Objects And Relationships Link

If we’re going to take this idea for a spin we’ll need to set up a simple Jekyll site and then build our objects and relationships. If you want to see the final product you can grab it from this GitHub repo5. (Note: You’ll have to use the terminal for some of this, but it’s pretty basic usage, I promise.)

Installing Jekyll Link

If you’re on a Mac this is pretty easy. Ruby is already installed, you just need to install Jekyll. Open up the terminal and type:

gem install jekyll

This will install the Jekyll Ruby gem and its dependencies. Once it’s done running, that’s it: you’ve got Jekyll.

Setting Up Your Site Link

Now we need to get a Jekyll scaffold started. I store all of my web projects in a folder on my Mac called Sites, in the home folder. So first I need to navigate to it with:

cd ~/Sites

Then I can generate a folder with the proper files and structure with this command:

jekyll new my-new-site

You can replace “my-new-site” with whatever you want to call your project. What you’ll get is a folder with that name, and all the right files inside it.

Pop open the Finder and navigate to your new folder to see what’s inside. You should see something like this:

A Mac OS X Finder window showing the initial Jekyll file scaffold.6
A Mac OS X Finder window showing the initial Jekyll file scaffold. (View large version7)

Since we don’t need everything Jekyll offers we’re going to delete a few files and folders first. Let’s toss /_includes, /_posts, /_sass, about.md and feed.xml.

Configuration Link

Now we’ll set up our site-wide configurations. Open up _config.yml. There’s a bunch of introductory stuff in there. I’ll just delete that and replace it with my preferred configurations. Here’s the new configuration for this project:

permalink: pretty

collections:
  projects
  people

I’ve made it so that my URLs will look like /path/to/file/ instead of /path/to/file.html which is just a personal preference. I’ve also established two collections: projects and people. Any new collection has to be added to the configuration file.

Now I can make folders for these collections in my project:

A Mac OS X Finder window showing collection folders added to the project folder.8
A Mac OS X Finder window showing collection folders added to the project folder. (View large version9)

The folder names have to start with the _ (underscore) character so that Jekyll knows what to do with them.

Making Some Objects Link

The first objects we’ll make will be our people. We’re going to use Markdown to create these files so they’ll be nice and clean but still generate proper, semantic HTML. You can see I’ve made some files for figures from American history (this may or may not be related to the fact that I’ve been listening to Hamilton10 non-stop for a month now):

A Mac OS X Finder window showing the files for each person object added to the people collection.11
A Mac OS X Finder window showing the files for each person object added to the people collection. (View large version12)

The attributes we’ll put in our file for a person will be:

---
object-id:
first-name:
last-name:
job:
listing-priority:
wikipedia-url:
---

We’ll use the object-id to refer specifically to any one of these objects later. We’ll split first and last name so we can select which combination to use in various places (if your system calls for this) and we’ll use job to define what they do. (I’m avoiding ‘title’ because that’s already a variable13 that pages in Jekyll have by default.) I’ve also included an attribute for listing priority which will allow me to sort each person according to whim, but you can also sort by some built-in methods such as alphabetical or numerical. Finally, we have a field for a link to the person’s Wikipedia page.

All of this is contained between three hyphens at the top and bottom to define it as YAML front-matter14. The content of each bio will go after the YAML and can be an arbitrary amount and structure of HTML (but we’ll use Markdown formatting to keep everything nice and clean).

A completely populated person object looks like this (the content is truncated for clarity):

---
object-id: alexander-hamilton
first-name: Alexander
last-name: Hamilton
job: 1st United States Secretary of the Treasury
listing-priority: 1
wikipedia-url: https://en.wikipedia.org/wiki/Alexander_Hamilton
---

Alexander Hamilton (January 11, 1755 or 1757 – July 12, 1804) was...

And here’s a project object (the content is truncated for clarity):

---
object-id: united-states-coast-guard
title: United States Coast Guard
featured: true
featured-priority: 2
listing-priority: 1
architect-id: alexander-hamilton
wikipedia-url: https://en.wikipedia.org/wiki/United_States_Coast_Guard
---

The United States Coast Guard (USCG) is...

This one has a few differences. I’ve set a featured attribute. If a project is featured then it gets listed on the home page. All projects will get listed on the projects page. We also have our preferred sort order set for each placement. And we’ve included a reference to the id of the person who created the project, so we can refer directly to them later.

Generating Pages From Our Objects Link

By altering my _config.yml file I can create pages for each of these objects.

permalink: pretty

collections:
  projects:
    output: true
  people:
    output: true

Setting output: true on each collection causes a page to be generated for each object inside it. But because our objects have no content in their files, they currently don’t output any data, which means we’ll just get empty pages. Let’s build a layout template to do that for us.

This file will go in the _layouts folder. But first, we have a default.html file in there to deal with. This will hold any markup that’s consistent across all of our HTML files.

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
		<title>{{ page.title }}</title>
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<link rel="stylesheet" href="/css/styles.css" />
	</head>
	<body>
		<header role="banner">
			...
		</header>

		<div role="main">
			<div class="container">
				{{ content }}
			</div>
		</div>

		<footer role="contentinfo">
			...
		</footer>
	</body>
</html>

You’ll notice a Liquid tag that looks like this: {{ content }}. Every file that gets rendered into a page by Jekyll needs a template specified for it. Once you specify its template, the content from that file gets rendered into the location of the {{ content }} tag in the layout template. Now we don’t have to repeat stuff that will be on every page.

Next, we’ll build a unique layout template for our person objects. That will look like this:

---
layout: default
---

<header class="intro person-header">
	<h1>{{ page.first-name }} {{ page.last-name }}</h1>
	<h2>{{ page.job }}</h2>
</header>

<div class="person-body">
	{{ page.content }}
	<a href="{{ page.wikipedia-url }}">Read more about {{ page.first-name }} {{ page.last-name }} on Wikipedia</a>
</div>

This file specifies that its code gets inserted into the default layout template, and then its markup gets populated from data in the person object files.

The last step is to make sure each person object specifies that it uses the person.html layout file. Normally, we’d just insert that into the YAML in our person files like so:

---
object-id:
first-name:
last-name:
job:
listing-priority:
wikipedia-url:
layout: person
---

But I prefer to make the data in my object files only contain attributes relevant to the content model. Fortunately, I can alter my _config.yml file to handle this for me:

exclude:
  - README.md

permalink: pretty

collections:
  projects:
    output: true
  people:
    output: true

defaults:
  - scope:
      type: projects
    values:
      layout: project
  - scope:
      type: people
    values:
      layout: person

Now my site knows that any object in the project collection should use the project layout template, and any object in the people collection should use the person layout. This helps me keep my content objects nice and clean.

Displaying Objects On A Listing Page Link

Whether we choose to output pages for our objects or not, we can list them out and sort by different parameters. Here’s how we’d list all of our projects on a page:

---
layout: default
title: Projects
---

<header class="intro">
	<h1>{{ page.title }}</h1>
</header>

<div class="case-studies-body">
	<ul class="listing">
		{% assign projects = site.projects | sort: 'listing-priority' %}
		{% for project in projects %}
		<li>
			<h2><a href="{{ project.url }}">{{ project.title }}</a></h2>
			{{ project.content }}
		</li>
		{% endfor %}
	</ul>
</div>

What we’ve done is create a <ul> to put our list inside. Then, we’ve created a variable on the page called projects, and assigned all of our project objects to it, and sorted them by the listing-priority variable we created in each one. Finally, for every project in our projects variable we output a <li> that includes data from the attributes in each file. This gives us a highly controllable list of our project objects with links to their unique pages.

On the home page, instead of displaying all projects we’ll show only our featured ones:

<ul class="listing">
	{% assign projects = site.projects | where: "featured", "true" | sort: 'featured-priority' %}
	{% for project in projects %}
	<li>
		<h3>{{ project.title }}</h3>
		<a href="{{ project.url }}">Learn about {{ project.title }}</a>
	</li>
	{% endfor %}
</ul>

Any project object that has the featured attribute set to true will render onto this page, and they’ll be sorted by the special priority ordering we’ve set for featured projects.

These are pretty simple examples of how to output and sort objects, but they demonstrate the different capabilities we can create for organizing content.

Linking To A Specific Object Link

The last feature we’re going to build is linking to a specific object. This is something you might want to do if you’re linking an author to a blog post or something similar. In our case, we’re going to attach a person to the project they are commonly associated with. If you remember, our project object has an architect-id attribute and our people each have an object-id attribute. We can attach the correct person to a specific project using these attributes.

Here is our project layout template:

---
layout: default
---
{% assign architect = site.people | where: "object-id", page.architect-id  | first %}

<header class="intro project-header">
	<h1>{{ page.title }}</h1>
	<p>Architected by: <a href="{{ architect.url }}">{{ architect.first-name }} {{ architect.last-name }}</a></p>
</header>
<div class="project-body">
	{{ page.content }}
	<a href="{{ page.wikipedia-url }}">Read more about {{ page.title }} on Wikipedia</a>
</div>

Line 4 creates a variable called architect and searches all of our people objects for any with an object-id that matches the architect-id attribute from a project. We should assign object-ids so that only one result is ever returned, but to make sure we only get one answer and refer to it instead of our list of one item, we have to set | first at the end of our {% assign %} Liquid tag. This gets around a limitation of Jekyll where objects in collections don’t have unique IDs to begin with. There’s another feature called data that does allow unique IDs, but it doesn’t easily output pages or give us the ability to sort our objects; working around the limitations of collections was an easier and cleaner way to get the functionality we want.

Now that the page has a unique object that represents the architect of that project, we can call its attributes with things like the architect’s first name and the URL to their Wikipedia page. Voila! Easy linking to objects by unique ID.

Wrapping Up Link

There are some great further features that can established by digging into the Jekyll docs15 in more detail, but what we have here are the basics of a good content modeling prototype: the ability to define different types of objects, the attributes attached to those objects, and IDs that allow us to call specific objects from anywhere. We also get highly flexible logic for templating and outputting our objects in various places. Best of all, the whole system is simple and human-readable, and outputs plain HTML for use elsewhere if necessary.

For communication purposes, we now have a platform-independent clickable prototype (a real website) that will define the system better than a PDF with a bunch of diagrams ever could. We can alter our content model on the fly as we learn new things and need to adapt. We can get the designer and developer into the system to establish their patterns and front-end architecture because it will accept any markup and CSS they want to use. We can even get content editors into it by setting them up with access through a GitHub GUI or a hosting platform that allows the use of a visual editor such as Prose.io16, GitHub Pages17, CloudCannon18 or Netlify19.

And none of this ties a person to learning platform-specific ways of working, allowing them to instead work from a conceptual level that’s focused on users and not technology.

(rb, jb, og, ml)

Footnotes Link

  1. 1 https://jekyllrb.com/
  2. 2 https://docs.shopify.com/themes/liquid-documentation/basics
  3. 3 http://jekyllrb.com/docs/collections/
  4. 4 https://en.wikipedia.org/wiki/YAML
  5. 5 https://github.com/javasteve99/jekyll-content-modeling-example
  6. 6 https://www.smashingmagazine.com/wp-content/uploads/2016/02/01-jekyll-scaffold-files-opt.png
  7. 7 https://www.smashingmagazine.com/wp-content/uploads/2016/02/01-jekyll-scaffold-files-opt.png
  8. 8 https://www.smashingmagazine.com/wp-content/uploads/2016/02/02-project-with-collection-folders-opt.png
  9. 9 https://www.smashingmagazine.com/wp-content/uploads/2016/02/02-project-with-collection-folders-opt.png
  10. 10 https://en.wikipedia.org/wiki/Hamilton_%28musical%29
  11. 11 https://www.smashingmagazine.com/wp-content/uploads/2016/02/03-files-for-people-objects-opt.png
  12. 12 https://www.smashingmagazine.com/wp-content/uploads/2016/02/03-files-for-people-objects-opt.png
  13. 13 http://jekyllrb.com/docs/variables/
  14. 14 http://jekyllrb.com/docs/frontmatter/
  15. 15 http://jekyllrb.com/docs/home/
  16. 16 http://prose.io/
  17. 17 https://pages.github.com/
  18. 18 http://cloudcannon.com/
  19. 19 https://github.com/netlify/netlify-cms
SmashingConf Barcelona 2016

Hold on, Tiger! Thank you for reading the article. Did you know that we also publish printed books and run friendly conferences – crafted for pros like you? Like SmashingConf Barcelona, on October 25–26, with smart design patterns and front-end techniques.

↑ Back to top Tweet itShare on Facebook

Advertisement

Steve Hickey is a UX strategist and product design educator, cultivating a simple, no-bs approach to building things that matter. Originally trained in print design, Steve switched to the web during his final year in the visual design program at UMass Dartmouth. Now he works with East Coast Product in Boston, MA where he leads the Product Design team and builds experiences for companies that understand success comes from putting their users first. He's also the former Director of AUX, a user experience apprenticeship program, as well as a writer and conference speaker, and a former podcast co-host.

  1. 1

    Super post Steve!

    Because this article is a good starting point for new Jekyll users it’s worth to mention that the installation on new Mac OS X El Capitan is a bit more complicated, because of the new system restrictions in OS.

    sudo gem install -n /usr/local/bin jekyll

    More about it here:

    http://jekyllrb.com/docs/troubleshooting/#jekyll-amp-mac-os-x-1011

    0
    • 2

      Thanks Pawel, glad you liked it. I’ve got some weird permissions stuff that seems to persist from my old system, so I haven’t come across that problem, but a few people in my office have. Thanks for posting the fix.

      0
    • 3

      If you use rbenv you won’t have such issues, since gems are installed at a different location.

      0
      • 4

        I know about it. Another way is to use Ruby installed by brew. For me it sounds like overkill, because Jekyll is literally the only one ruby thing that I use.

        Thanks for posting avioli. That may be helpful for other users.

        0
  2. 5

    A ready-to-go tool you should also check out is GatherContent. It not only is platform-agnostic, but allows you to build out your navigation, create templates, and use basic data entry types. Bonus is they have a REST API that is capable of dumping the whole “site” or selective pieces of it in easily-consumed and well-linked JSON. No dealing with theming or CMS-specific constructs, ramp up time is very fast.

    1
    • 6

      Nice idea, thanks for posting. I still need to do some experimenting with getting content authoring into this workflow seamlessly, seems like a good solution.

      0
  3. 7

    I would like to point out that my experience shows that id is not a suitable key to use.

    On my setup it was populated with /people/alexander-hamilton (for a person), thus the query for the architect (on the project) did not work. I had to change the value in the project to architect-id: /people/alexander-hamilton and then it all worked.

    Confirming the value was easy – just put {{ page.id }} within your person.html and you’ll see the value. It turns out the id is pre-populated by the file’s path+name-extension.

    Instead I renamed the key to uid and renamed all use of id to uid.

    1
    • 8

      Thanks for the suggestion, I’ve updated the example repo to fix this. That particular attribute has been changed to object-id to be very explicit to anyone following along with the project.

      0
  4. 9

    Annette Arabasz

    February 24, 2016 2:17 am

    Great post! I had heard of Jekyll before, but your post gave me the confidence to try it. It was really easy to follow your instructions and set up a basic Jekyll project. Thanks Steve!

    1
  5. 10

    This is super, thank you. As a novice Jekyll user I have scoured the web for how to have 2 ‘sets’ of what I was calling posts – for blog posts and for portfolio work. No Googling was showing how to do this – but you call them content types and have more than one and it works! Thanks very much – looking forward to finally creating the site I want.

    0
    • 11

      No prob! I was trying to do this too about 3 years ago and getting nowhere with it. I was quite happy when collections popped up as new functionality.

      1
  6. 12

    Thanks for a great post, Steve! I was already using Jekyll for blogging, but you’ve inspired me to explore further. I agree that prototyping a content model is a great use case for Jekyll.

    Regarding the recommendation of GatherContent: it’s a great tool, but it doesn’t allow for the kinds of aggregating and contextual logic you talk about towards the end of your article: generating lists, automatically linking content objects based on shared attributes, etc. I’d see the two as serving different (but potentially overlapping) purposes.

    1
  7. 13

    Great article, this is perfect for a project I’m beginning!

    From where did you get the “where” filter for the collection? I don’t see the “where” filter anyplace in the Liquid documentation?

    {% assign architect = site.people | where: “id”, page.architect-id…

    Thanks!

    0

↑ Back to top