Jekyll is a static site generator. You write content in Markdown, define layouts in HTML, and Jekyll compiles everything into a folder of plain files ready to serve from any host — no database, no PHP, no server process running 24/7.
This guide covers the fundamentals: what Jekyll does, how it’s structured, and when it’s the right choice.
What Jekyll Actually Does
Jekyll takes a folder of source files and builds a _site/ folder of flat HTML. That’s the entire job.
source/ → _site/
index.html index.html
_posts/ blog/
2026-01-01-hello.md 2026/hello/index.html
_layouts/
default.html
assets/
css/style.css assets/css/style.css
During that build, Jekyll processes Liquid template syntax ({{ page.title }}, {% for post in site.posts %}), converts Markdown to HTML, and applies your layouts.
The output in _site/ has no dependencies. Drop it on Cloudflare Pages, GitHub Pages, Netlify, an S3 bucket, an nginx server — it works everywhere.
Core Concepts
Layouts
Layouts live in _layouts/. They define the outer shell of your pages — the <head>, navigation, footer.
<!-- _layouts/default.html -->
<!DOCTYPE html>
<html>
<head>
<title>{{ page.title }}</title>
</head>
<body>
{% include nav.html %}
{{ content }}
{% include footer.html %}
</body>
</html>
{{ content }} is where the page’s own content gets injected. Every page or post declares which layout to use in its front matter:
---
layout: default
title: About Me
---
Front Matter
Front matter is the YAML block between --- delimiters at the top of any file. Jekyll reads it as metadata you can reference in templates.
---
layout: post
title: "My First Post"
date: 2026-03-05
categories: [Tutorial, Jekyll]
---
You can define any variable you want — author, thumbnail, featured: true — and use it in your templates as {{ page.author }}, {{ page.thumbnail }}, etc.
Posts
Posts live in _posts/ and follow the naming convention YYYY-MM-DD-title.md. Jekyll parses the date from the filename and makes it available as {{ post.date }}.
<!-- _posts/2026-03-05-jekyll-101.md -->
---
layout: post
title: "Jekyll 101"
---
Your post content in Markdown here.
You loop over posts in templates like this:
{% for post in site.posts %}
<article>
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
<time>{{ post.date | date: "%B %d, %Y" }}</time>
</article>
{% endfor %}
Includes
Reusable HTML snippets live in _includes/. Pull them in anywhere with {% include filename.html %}.
_includes/
nav.html
footer.html
cta-box.html
Collections
Collections extend the post model to any content type — team members, projects, products, documentation pages. Define them in _config.yml:
collections:
projects:
output: true
permalink: /work/:name/
Then create files in _projects/my-project.md and loop over them with {% for project in site.projects %}.
Data Files
_data/ holds YAML, JSON, or CSV files you can reference in templates without building a full collection.
# _data/team.yml
- name: Alice
role: Designer
- name: Bob
role: Developer
{% for person in site.data.team %}
<p>{{ person.name }} — {{ person.role }}</p>
{% endfor %}
_config.yml
The site’s configuration file. Defines the site title, URL, plugin list, and any global variables you want available as {{ site.variable_name }} across all templates.
title: "My Site"
url: "https://mysite.com"
description: "A personal blog about design and code."
plugins:
- jekyll-seo-tag
- jekyll-sitemap
# Custom variables
twitter_handle: "@myhandle"
How a Build Works Step by Step
- Jekyll reads
_config.yml - Loads all plugins
- Reads
_data/files intosite.data - Reads posts from
_posts/intosite.posts - Reads collections into
site.collection_name - Processes each page: evaluates Liquid, applies layout, converts Markdown
- Copies
assets/as-is - Writes everything to
_site/
The result is deterministic. Same input always produces the same output.
When Jekyll Is the Right Choice
Jekyll is a good fit when:
- Content changes infrequently — blogs, portfolios, documentation, landing pages
- Performance matters — static files on a CDN are as fast as it gets
- Security is a concern — no server process to exploit, no database to inject
- You want portability — pure files, any host, no lock-in
It’s not the right fit when:
- You need user-generated content or real-time data
- Non-technical editors need to publish without any setup
- You have thousands of pages with complex relationships (build times become a factor)
The Part Nobody Mentions
The learning curve isn’t in understanding Jekyll’s concepts — those are straightforward. The friction comes from:
Liquid syntax. It’s readable but unfamiliar. Filters like {{ post.date | date: "%Y" }}, conditionals, loop offsets — these take time to get comfortable with.
Theme architecture. Open-source Jekyll themes vary wildly in structure. Some use Gem-based themes (layouts live inside the gem, not your repo). Others bundle everything in the repo. Knowing how to override without forking the whole theme is non-obvious at first.
CSS compilation. Themes that use Sass require understanding the _sass/ folder structure and variable overrides. A small change to a color can require tracing through three layers of imports.
If you’re comfortable with HTML and CSS but want to skip the Liquid and build scaffolding, a visual builder — like Jekyll Builder — handles the template layer so you can focus on design and content.
Further Reading
- Official Jekyll documentation — comprehensive reference
- Deploy Jekyll to Cloudflare Pages — step-by-step deployment guide
- Best Jekyll Themes in 2026 — curated starting points for new projects
Want to skip the Liquid learning curve entirely? Jekyll Builder gives you a visual canvas that generates standard Jekyll templates — no Liquid required to get started.