Jekyll Directory Structure Explained: What Every File and Folder Does
A complete walkthrough of Jekyll's directory structure — every folder, file, and naming convention explained with practical examples for beginners and theme developers.
When you first open a Jekyll project, the folder structure can look confusing. Underscores everywhere, a _site folder that appears after building, special filenames with dates. Once you understand what each piece does, it all makes sense — Jekyll’s structure is actually quite logical.
Here is a complete reference for every file and folder in a Jekyll project.
The full structure at a glance
my-jekyll-site/
│
├── _config.yml ← site configuration
├── Gemfile ← Ruby dependency list
├── Gemfile.lock ← locked dependency versions
│
├── _posts/ ← blog post files
├── _drafts/ ← unpublished drafts
├── _pages/ ← static page files (optional convention)
│
├── _layouts/ ← HTML wrapper templates
├── _includes/ ← reusable HTML fragments
├── _sass/ ← Sass/SCSS partials
│
├── _data/ ← structured data (YAML, JSON, CSV)
├── _plugins/ ← custom Ruby plugins
│
├── _collections/ ← custom collection directories
│ ├── _themes/
│ └── _authors/
│
├── assets/ ← static files (CSS, JS, images)
│ ├── css/
│ ├── js/
│ └── images/
│
├── index.html ← homepage
└── _site/ ← generated output (do not edit)
_config.yml
The master configuration file. Controls site-wide settings, plugins, collections, permalink structure, build options, and any custom data you want available in all templates as site.* variables.
title: "My Jekyll Site"
url: "https://example.com"
plugins:
- jekyll-feed
- jekyll-seo-tag
Jekyll reads this file at startup only — restart the server after changes.
Gemfile and Gemfile.lock
Gemfile lists your Ruby dependencies — Jekyll itself and any plugins:
source "https://rubygems.org"
gem "jekyll", "~> 4.3"
gem "jekyll-feed"
gem "jekyll-seo-tag"
Gemfile.lock is auto-generated by Bundler and records the exact version of every gem installed. Commit this file — it ensures anyone who clones your project gets identical gem versions.
Never edit Gemfile.lock by hand. Update it with bundle update.
_posts/
All blog posts live here as Markdown (or HTML) files. Jekyll requires a specific naming convention:
YYYY-MM-DD-title-of-post.md
Examples:
_posts/
├── 2026-08-03-jekyll-directory-structure.md
├── 2026-07-29-jekyll-front-matter-guide.md
└── 2026-01-15-my-first-post.md
The date in the filename sets the post’s default date. Jekyll uses it for sorting, URL generation, and the page.date variable. Posts are accessible via site.posts in templates.
_drafts/
Drafts are posts without a date in the filename. They live in _drafts/ and are excluded from normal builds:
_drafts/
├── my-unfinished-post.md
└── ideas-for-later.md
To preview drafts locally: bundle exec jekyll serve --drafts
Drafts are never built in production unless you explicitly pass --drafts to the build command.
_pages/ (convention, not built-in)
Jekyll has no built-in _pages/ directory — but it is a widely used convention for storing static pages separately from posts. Files here are processed exactly like files in the root directory.
_pages/
├── about.md
├── contact.md
├── themes.html
└── faq.md
To make Jekyll process files from _pages/, either list it explicitly in _config.yml or keep your pages in the root directory. Many themes include _pages/ in their configuration via:
include:
- _pages
Or use a collection:
collections:
pages:
output: true
permalink: /:name/
_layouts/
HTML templates that wrap page content. Jekyll replaces `<div class="read-progress" id="read-progress"></div>
Jekyll Remote Themes: How to Use Any GitHub Theme Without Installing It
Jekyll remote themes let you use any Jekyll theme hosted on GitHub without installing gem files. A complete guide to setup, customisation, and switching themes.
Remote themes are one of the most convenient ways to use a Jekyll theme — especially on GitHub Pages. Instead of installing a theme as a Ruby gem, you reference it directly from its GitHub repository. The hosting platform (GitHub Pages, Netlify, Cloudflare Pages) fetches the theme files at build time and applies them to your site.
What is a remote theme?
A remote theme is a Jekyll theme hosted as a public GitHub repository that you can apply to your site without manually copying its files. You reference it with a single remote_theme setting in _config.yml.
This is different from installing a theme as a gem (gem "minima" in your Gemfile). With remote themes:
- You do not need to add a gem to your
Gemfilefor the theme - The theme files are fetched from GitHub at each build
- You can use any version-compatible public Jekyll repository as a theme
- Updating the theme means changing one line in
_config.yml
The jekyll-remote-theme plugin
Remote themes are powered by the jekyll-remote-theme plugin, maintained by GitHub. It is automatically enabled on GitHub Pages. For other platforms (Netlify, Cloudflare Pages, Vercel), you add it yourself.
Setting up on GitHub Pages
GitHub Pages has jekyll-remote-theme built in. Just add remote_theme to _config.yml:
remote_theme: mmistakes/minimal-mistakes
That is the complete setup. No Gemfile changes, no plugin installation.
Setting up on other platforms
Add the plugin to your Gemfile:
group :jekyll_plugins do
gem "jekyll-remote-theme"
end
Add it to _config.yml:
plugins:
- jekyll-remote-theme
remote_theme: mmistakes/minimal-mistakes
Run bundle install and commit Gemfile.lock.
Specifying a remote theme
The remote_theme value follows the format owner/repository:
# Use the latest commit on the default branch
remote_theme: mmistakes/minimal-mistakes
# Pin to a specific tag or release
remote_theme: mmistakes/minimal-mistakes@4.26.0
# Pin to a specific branch
remote_theme: mmistakes/minimal-mistakes@master
# Pin to a specific commit SHA (most stable)
remote_theme: mmistakes/minimal-mistakes@d9c6f33
Pinning to a version is strongly recommended for production sites. Without pinning, your site rebuilds with whatever is the latest commit on the theme’s default branch — a theme update could unexpectedly change your site’s appearance.
Popular themes available as remote themes
These themes are widely used and fully support remote_theme:
| Theme | remote_theme value |
Best for |
|---|---|---|
| Minimal Mistakes | mmistakes/minimal-mistakes |
Blogs, documentation |
| Just the Docs | just-the-docs/just-the-docs |
Documentation |
| Cayman | pages-themes/cayman@v0.2.0 |
Project pages |
| Slate | pages-themes/slate@v0.2.0 |
Project pages |
| Minima | jekyll/minima |
Simple blogs |
| al-folio | alshedivat/al-folio |
Academic portfolios |
Customising a remote theme
The power of remote themes is that you can override any file from the theme without touching the theme’s repository. Jekyll’s file resolution works like this: your files take precedence over the theme’s files.
To override a theme file, create the same file in your project. Jekyll uses yours and ignores the theme’s version.
Overriding a layout
If the theme has _layouts/post.html and you want to customise it:
- Copy the file from the theme’s GitHub repository
- Place it at
_layouts/post.htmlin your project - Modify it as needed
Your _layouts/post.html now overrides the theme’s.
Overriding an include
# Theme's version (not in your repo)
_includes/nav.html
# Your override (in your repo — takes precedence)
_includes/nav.html ← your custom nav
Adding custom CSS
Most remote themes look for a assets/css/style.scss or similar entry point. Add your custom styles:
// assets/css/custom.scss
---
---
// Import the theme's base styles
@import "jekyll-theme-primer";
// Your overrides
.site-header {
background: #1a1a2e;
}
.post-title {
font-family: "Georgia", serif;
}
For Minimal Mistakes specifically, add to _config.yml:
# Minimal Mistakes custom CSS
head_scripts:
- /assets/css/custom.css
Or create _sass/custom.scss and import it in the theme’s skin file.
Customising theme configuration
Remote themes typically expose configuration options via _config.yml. Check the theme’s documentation for available settings:
# Minimal Mistakes theme settings
minimal_mistakes_skin: "dark" # or: air, aqua, contrast, default, dirt, neon, mint, plum, sunrise
# Site navigation
header_pages:
- about.md
- contact.md
# Author profile
author:
name: Marcus Webb
avatar: /assets/images/avatar.jpg
bio: "Developer and technical writer."
links:
- label: GitHub
icon: "fab fa-fw fa-github"
url: "https://github.com/marcuswebb"
Remote themes vs gem-based themes vs downloaded themes
| Remote theme | Gem theme | Downloaded theme | |
|---|---|---|---|
| Files in your repo | No | No | Yes |
| Easy updates | Yes (change one line) | Yes (update gem version) | No (manual) |
| Full customisation | Yes (override files) | Yes (override files) | Yes (edit directly) |
| Works on GitHub Pages | Yes | Whitelist only | Yes |
| Requires internet at build | Yes | No | No |
| Build time impact | Slightly slower | Minimal | Minimal |
When to use a remote theme
- You want a well-maintained community theme without vendoring its files
- You are on GitHub Pages and the theme is not a whitelisted gem
- You want easy updates by changing a version pin in
_config.yml
When to use a gem theme
- Your platform supports all gems (Netlify, Cloudflare Pages)
- The theme is distributed as a gem on RubyGems
- You want offline builds without fetching from GitHub
When to download and own the theme files
- You plan to heavily customise the theme beyond small overrides
- You want no external dependency at build time
- You bought a premium theme and have the source files
Building on top of a remote theme
Many sites use a remote theme as a foundation and add content on top:
my-jekyll-site/
├── _config.yml ← remote_theme: mmistakes/minimal-mistakes
├── _posts/ ← your content
├── _pages/ ← your pages
├── _data/ ← your data files
├── _layouts/ ← any layout overrides
├── _includes/ ← any include overrides
├── _sass/ ← custom styles
└── assets/
└── images/ ← your images
The theme provides the layout, CSS, and JavaScript. You provide the content and any customisations. The separation is clean.
Common remote theme issues
Build fails: “Remote theme error”
The most common cause is a rate limit on the GitHub API. Remote themes fetch from GitHub at build time; too many builds in a short window can hit unauthenticated API limits.
Fix: Set a GITHUB_TOKEN environment variable in your build platform. This uses authenticated API requests with higher limits:
# Netlify / Cloudflare Pages environment variable
GITHUB_TOKEN: your_github_personal_access_token
Theme files not updating
If you pinned to a branch (@main) and updated the remote theme, old cached files may still be served. Clear your build platform’s cache or pin to the latest commit SHA.
CSS not loading after customisation
Check your baseurl in _config.yml. Remote themes often have hardcoded paths that conflict with a non-empty baseurl. For a root-domain site, baseurl: "" is correct.
Layout not found
If you reference a layout that exists in the remote theme but get a “layout not found” error, ensure jekyll-remote-theme is listed in your plugins array (for non-GitHub Pages platforms) and in your Gemfile.
Switching themes
Switching remote themes is as simple as changing one line in _config.yml:
# Before
remote_theme: mmistakes/minimal-mistakes
# After
remote_theme: pages-themes/cayman@v0.2.0
Rebuild your site. The new theme applies immediately. Note that any layout names, front matter variables, or configuration keys specific to the old theme may not exist in the new one — you will likely need to update those as well.
Remote themes on GitHub Pages
GitHub Pages enables jekyll-remote-theme by default since 2017. Any public GitHub repository with a valid Jekyll theme structure can be used as a remote theme.
This is particularly useful for using themes that are not on the GitHub Pages gem whitelist — which only includes about 15 official themes. With remote_theme, the entire Jekyll ecosystem is available.
Remote themes are one of the easiest ways to get a professional Jekyll site up and running quickly. Combined with selective file overrides and _config.yml customisation, you can build a unique site on top of a polished theme foundation without managing hundreds of theme files yourself.
</button> ` in a layout with the page’s rendered output.
_layouts/
├── default.html ← base shell (<html>, <head>, nav, footer)
├── page.html ← inherits default, adds page container
├── post.html ← inherits default, adds article structure
└── home.html ← inherits default, adds hero section
Layouts can inherit from each other via front matter:
---
layout: default ← this layout wraps inside default.html
---
_includes/
Reusable HTML fragments embedded in layouts or content with {% include filename.html %}.
_includes/
├── head.html ← <head> contents
├── nav.html ← navigation bar
├── footer.html ← footer
├── analytics.html ← analytics scripts
├── components/
│ ├── card.html ← theme card component
│ └── badge.html ← badge component
└── sections/
├── home-hero.html ← homepage hero section
└── home-newsletter.html
Unlike layouts, includes can be used anywhere — in layouts, in other includes, even mid-content in Markdown files.
_sass/
Sass/SCSS partial files that Jekyll compiles into CSS. Files starting with _ are partials (not compiled to standalone CSS files):
_sass/
├── _variables.scss ← colour and spacing tokens
├── _base.scss ← reset, body, typography
├── _nav.scss ← navigation styles
├── _cards.scss ← card component styles
├── _post.scss ← blog post styles
└── _utilities.scss ← helper classes
These partials are imported by a main SCSS entry file in assets/css/:
/* assets/css/main.scss */
---
---
@import "variables";
@import "base";
@import "nav";
@import "cards";
@import "post";
@import "utilities";
The empty front matter (---
---) at the top tells Jekyll to process this file through Sass.
_data/
Structured data files in YAML, JSON, CSV, or TSV format. Accessible in all templates as site.data.filename:
_data/
├── navigation.yml → site.data.navigation
├── authors.yml → site.data.authors
├── faq.yml → site.data.faq
├── showcase.yml → site.data.showcase
└── bundle.yml → site.data.bundle
Example usage:
{% for item in site.data.navigation %}
<a href="{{ item.url }}">{{ item.title }}</a>
{% endfor %}
Useful for any structured content that does not need individual pages — navigation menus, team members, FAQs, testimonials, pricing tables.
_plugins/
Custom Ruby plugin files that extend Jekyll’s functionality. Files here are loaded automatically at build time:
_plugins/
├── my_generator.rb ← custom page generator
├── my_filter.rb ← custom Liquid filter
└── my_hook.rb ← Jekyll build hook
Note: Custom plugins in _plugins/ do not work on GitHub Pages (security restriction). They work on Netlify, Cloudflare Pages, and Vercel where you control the build environment.
Custom collection directories
Collections defined in _config.yml get their own _collectionname/ directory:
# _config.yml
collections:
themes:
output: true
permalink: /themes/:name/
authors:
output: true
permalink: /authors/:name/
_themes/
├── minimal-mistakes.md
├── chirpy.md
└── al-folio.md
_authors/
├── marcus-webb.md
└── sarah-chen.md
Collection items are available as site.themes and site.authors in templates.
assets/
Static files served directly — CSS, JavaScript, fonts, and images. Unlike _-prefixed directories, assets/ is copied to _site/ without processing (except for SCSS files with front matter).
assets/
├── css/
│ └── main.scss ← compiled to main.css
├── js/
│ ├── main.js
│ └── bookmarks.js
├── images/
│ ├── logo.png
│ ├── social-card.png
│ └── blog/
│ └── post-cover.webp
└── fonts/
└── inter.woff2
Reference assets in templates using relative_url:
<link rel="stylesheet" href="/assets/css/main.css">
<img src="/assets/images/logo.png" alt="Logo">
Root-level files
index.html or index.md — your homepage. Can use any layout.
404.html — custom 404 page. Most hosts serve this automatically for missing pages.
feed.xml or atom.xml — RSS/Atom feed (usually auto-generated by jekyll-feed).
sitemap.xml — XML sitemap (auto-generated by jekyll-sitemap).
robots.txt — instructions for search engine crawlers:
User-agent: *
Allow: /
Sitemap: https://example.com/sitemap.xml
_redirects — redirect rules for Netlify/Cloudflare Pages.
.gitignore — files to exclude from Git:
_site/
.jekyll-cache/
.sass-cache/
.bundle/
vendor/
node_modules/
_site/
The generated output — never edit files here directly. Jekyll wipes and rebuilds this directory on every build. It mirrors what your visitors see:
_site/
├── index.html
├── about/
│ └── index.html
├── blog/
│ ├── index.html
│ └── jekyll-directory-structure/
│ └── index.html
├── assets/
│ ├── css/
│ │ └── main.css ← compiled from main.scss
│ └── js/
│ └── main.js
├── feed.xml
└── sitemap.xml
Add _site/ to .gitignore — deploy from your build pipeline, not from a committed _site/.
.jekyll-cache/
Jekyll’s internal build cache. Speeds up incremental builds by storing processed files. Safe to delete if you see stale content — Jekyll regenerates it. Add to .gitignore.
Files Jekyll ignores by default
Jekyll automatically excludes these from the build output:
GemfileandGemfile.locknode_modules/- Any file or directory starting with
.(dotfiles) - Any file or directory starting with
_(except those explicitly handled) - Files listed in
exclude:in_config.yml
The build flow
When you run bundle exec jekyll build:
- Jekyll reads
_config.yml - Reads all files in
_posts/,_pages/,_data/, collections - Processes files with front matter through Liquid templating
- Applies layouts (wrapping content in layout HTML)
- Compiles Sass/SCSS to CSS
- Copies static assets unchanged
- Writes everything to
_site/
Understanding this flow makes it clear why _ directories are special (processed by Jekyll) while assets/ is not (copied as-is), and why changes to _config.yml require a restart.