DEV Community

Cover image for How to make a Gem out of your Jekyll theme!
Brennan K. Brown
Brennan K. Brown

Posted on

How to make a Gem out of your Jekyll theme!

If you’ve ever wanted to share your Jekyll site’s look-and-feel as a reusable theme, you’ve probably found the docs thin on “the whole process.”

This post is a practical, repeatable playbook for converting a real Jekyll site into a theme gem—covering structure, portability, a local sandbox, versioning, and release. It’s based on extracting the Purelog theme into a gem, but it works for any Jekyll site following similar conventions.

What you’ll get by the end:

  • A theme gem living under theme/ with _layouts/, _includes/, and assets/.
  • A working sandbox site under sandbox/ to iterate quickly.
  • A clean assets strategy (goodbye placeholder files, hello single CSS entrypoint).
  • Versioning, a changelog, and a release checklist you can reuse.

Who it’s for:

  • Site owners who want to share their style.
  • Maintainers who need repeatable packaging.
  • Teams standardizing multiple themes across sites.

NOTE: I am using my own Jekyll theme Purelog as an example throughout this guide.


1) Plan your repo structure

Keep your original site in the repo root; add a theme folder and a sandbox site.

/ (repo root)
  _config.yml
  _layouts/
  _includes/
  assets/
  ...
  theme/              # the theme gem lives here (what consumers will install)
  sandbox/            # a test site that uses the theme during development
  docs/               # changelog, guide, deployment notes
Enter fullscreen mode Exit fullscreen mode

Tip: Exclude theme/ and sandbox/ from your production build if your root is still a live site.


2) Scaffold the theme gem under theme/

Inside theme/:

  • _layouts/default.html, home.html, post.html, etc.
  • _includes/head.html, sidebar.html, footer.html, analytics.html, etc.
  • assets/ — real CSS/JS (no placeholders).
  • assets/css/purelog.css — your single CSS entrypoint that imports actual CSS.
  • lib/purelog.rb and lib/purelog/version.rb — Ruby entrypoint + version.
  • purelog.gemspec — gemspec that packages everything.
  • README.md and LICENSE — for consumers and legal clarity.

Example: theme/lib/purelog/version.rb

# frozen_string_literal: true
module Purelog
  VERSION = "0.1.0"
end
Enter fullscreen mode Exit fullscreen mode

Example: theme/purelog.gemspec (key parts)

Gem::Specification.new do |spec|
  spec.name        = "purelog"
  spec.version     = Purelog::VERSION
  spec.summary     = "Purelog Jekyll theme"
  spec.description = "A reusable Jekyll theme extracted from a site."
  spec.homepage    = "https://github.com/you/your-repo"
  spec.license     = "MIT"

  spec.files = Dir.chdir(__dir__) do
    Dir["lib/**/*", "_layouts/**/*", "_includes/**/*", "_sass/**/*", "assets/**/*", "README*", "LICENSE*"]
      .select { |f| File.file?(f) }
  end

  spec.add_runtime_dependency "jekyll", "~> 4.3"
  spec.required_ruby_version = ">= 3.0"
end
Enter fullscreen mode Exit fullscreen mode

Why pin jekyll to ~> 4.3? It respects semver and silences a warning about wide-open version ranges.


3) Make paths portable with relative_url

Ensure all theme links work regardless of baseurl. In includes and layouts, prefer:

<link rel="stylesheet" href="{{ "/assets/main.css" | relative_url }}">
<link rel="stylesheet" href="{{ "/assets/code.css" | relative_url }}">
<a href="{{ "/" | relative_url }}">Home</a>
Enter fullscreen mode Exit fullscreen mode

Bonus: Load third-party CSS with SRI + crossorigin attributes, and add SEO/Feed tags if you use jekyll-seo-tag and jekyll-feed.


4) Assets strategy that “just works”

  • Copy real assets into theme/assets/.
  • Create one entrypoint CSS file to keep things predictable:

theme/assets/css/purelog.css

/* Theme entrypoint */
@import url("../main.css");
@import url("../code.css");
Enter fullscreen mode Exit fullscreen mode

Had an SCSS entrypoint? Two options:

  • Convert to CSS entrypoint and stop compiling the SCSS (remove front matter/imports so Jekyll ignores it).
  • Or delete the SCSS file to avoid duplicate builds and conflicts.

5) Spin up a sandbox site for quick feedback

In sandbox/, create a minimal Jekyll site that uses your theme gem:

sandbox/_config.yml

title: Theme Sandbox
baseurl: ""
url: ""

theme: purelog

plugins:
  - jekyll-feed
  - jekyll-seo-tag
  - jekyll-paginate-v2
  - jekyll-sitemap

# Pagination defaults (v2 config)
pagination:
  enabled: true
  per_page: 5
  permalink: "/page/:num/"
  sort_field: "date"
  sort_reverse: true
Enter fullscreen mode Exit fullscreen mode

sandbox/index.md

---
layout: home
title: Home
pagination:
  enabled: true
---
Enter fullscreen mode Exit fullscreen mode

Commands:

bundle install
bundle exec jekyll build
bundle exec jekyll serve --detach --port 4001
Enter fullscreen mode Exit fullscreen mode

Pro tip: Add a handful of posts to test multi-page pagination under sandbox/_posts/.


6) Versioning, docs, and changelog

  • Version: bump theme/lib/purelog/version.rb per release.
  • Changelog: keep a docs/CHANGELOG.md (Keep a Changelog style works great).
  • Docs: a theme/README.md for consumers, and a “Theme Gem Guide” for maintainers with your exact process and checklists.

A short, repeatable release checklist pays dividends.


7) Build and verify the gem

Run from theme/:

gem build purelog.gemspec
# => Successfully built RubyGem: purelog-0.1.0.gem
Enter fullscreen mode Exit fullscreen mode

Common pitfalls and fixes:

  • Building from repo root → File lists don’t resolve. Fix: run from theme/.
  • Placeholder assets → Broken styles. Fix: copy real CSS/JS.
  • SCSS entrypoint conflict → Two files write to same CSS path. Fix: disable/convert the SCSS entrypoint.

8) Release checklist you can paste into issues

  • [ ] All internal links/assets use relative_url.
  • [ ] theme/_includes/head.html loads CSS/JS via relative_url.
  • [ ] theme/assets/ contains real, complete assets.
  • [ ] Single CSS entrypoint present at assets/css/purelog.css.
  • [ ] Any legacy SCSS entrypoint does not compile (or is removed).
  • [ ] theme/purelog.gemspec pins jekyll to ~> 4.3 and includes all files.
  • [ ] theme/lib/purelog/version.rb bumped (e.g., 0.1.0).
  • [ ] theme/README.md and theme/LICENSE present.
  • [ ] Sandbox builds and serves (bundle exec jekyll build, then serve).
  • [ ] Changelog updated with the new version entry.

9) Optional niceties

  • CI workflow to build the sandbox on PRs.
  • Netlify or GitHub Pages for a public theme demo.
  • Lighthouse and accessibility passes.
  • Document “minimal config” defaults in the README for quick starts.

10) Ready-to-reuse snippets

Sandbox Gemfile (local path during development):

source "https://rubygems.org"

gem "jekyll", "~> 4.3"

group :jekyll_plugins do
  gem "jekyll-feed"
  gem "jekyll-seo-tag"
  gem "jekyll-paginate-v2"
  gem "jekyll-sitemap"
end

# Use local theme gem
gem "purelog", path: "../theme"
Enter fullscreen mode Exit fullscreen mode

Search initialization (if you include a simple search):

<script src="{{ "/assets/simple-jekyll-search.js" | relative_url }}"></script>
<script>
  SimpleJekyllSearch({
    searchInput: document.getElementById('search-input'),
    resultsContainer: document.getElementById('results-container'),
    json: '{{ "/search.json" | relative_url }}'
  })
</script>
Enter fullscreen mode Exit fullscreen mode

Conclusion

Turning a Jekyll site into a reusable theme gem is less about magic and more about discipline:

  • Organize your repo with a clear theme/ and sandbox/.
  • Make paths portable with relative_url.
  • Centralize styling with a single CSS entrypoint.
  • Keep your gemspec honest and versioning intentional.

Once you’ve done it once, the next dozen themes become “copy, adapt, ship.”

If you’d like to see this playbook in action, check out the Purelog theme gem’s structure:

  • theme/_layouts/, theme/_includes/, theme/assets/
  • theme/lib/purelog.rb, theme/lib/purelog/version.rb
  • theme/purelog.gemspec
  • sandbox/ with pagination and sample content

Happy theming—and if you ship your own theme gem using this method, share it in the comments. I’d love to see what you build!

Top comments (0)