A Jekyll personal website needs Ruby installed correctly, which is the step most guides skim past and most people get stuck on; a _config.yml with a title, url and theme; posts written as Markdown files in _posts named with a date prefix; and either GitHub Pages building the site for you, which restricts you to a whitelisted set of plugins, or a GitHub Actions workflow building it yourself, which does not. A personal portfolio needs less than most themes ship with: an about section, a handful of work samples, and a way to get in touch, not a comment system or an analytics dashboard.
Jekyll predates most of the static site generators that get recommended today, and it still gets recommended itself, mostly because GitHub Pages was built to run it natively.
That native support is also where the friction lives: a Ruby install that behaves differently on every operating system, and a plugin whitelist that only applies if you let Pages build the site for you.
Table of contents
- Ruby install pain, and the fix
- _config.yml
- _posts and permalinks
- Themes
- The Pages-safe plugin whitelist
- Building yourself vs letting Pages build
- Custom domain, and what a portfolio actually needs
- How this fits the rest of the stack
- FAQ
Ruby install pain, and the fix
macOS ships a system Ruby that is old and that gem install writes into a system directory requiring sudo, which System Integrity Protection then partly blocks anyway. The fix is not to fight the system Ruby: install rbenv or asdf with Homebrew, install a current Ruby version under it, and gem install jekyll bundler runs cleanly in a user-owned path.
Windows has the opposite problem: no system Ruby at all, and the RubyInstaller package works but drags in native build tools for gems with C extensions. The lower-friction path on Windows is WSL, installing Ruby inside a Linux environment the same way macOS does, or skipping a local Ruby entirely and using the official jekyll/jekyll Docker image to build.
docker run --rm -v "$PWD:/srv/jekyll" jekyll/jekyll:latest jekyll build
_config.yml
_config.yml holds the site-wide settings: title, description, url (the site’s eventual domain, used to build absolute links), baseurl (a subpath, empty for a domain root), and markdown: kramdown. Settings here only take effect on the next build; jekyll serve does not hot-reload config changes the way it does for content.
Anything in _config.yml is also available in templates as site.whatever, which is how a theme’s header pulls the site title without it being hardcoded.
_posts and permalinks
Posts live in _posts, named YYYY-MM-DD-title.md, and the date in the filename sets the post’s date and default URL unless front matter overrides it. Front matter at the top of each file sets layout: post, title, and any tags or categories the theme uses.
- _posts/2026-09-22-first-post.md becomes /2026/09/22/first-post/ by default
- the permalink setting in _config.yml can change that pattern site-wide
- pages outside _posts, such as about.md or contact.md, use layout: page instead
Themes
A gem-based theme, set with theme: minima in _config.yml, ships its layouts and includes inside the gem itself, invisible in your repository, which keeps the project clean but makes deep customization awkward. A remote theme, set with remote_theme, works the same way but pulls from any public GitHub repo, which is how portfolio-specific themes like al-folio are usually installed.
Copying a theme’s files into the repository directly is the third option, and the right one once you are overriding more than a couple of includes; at that point you are maintaining a fork, not using a theme.
The Pages-safe plugin whitelist
If GitHub Pages builds the site for you, it runs a fixed version of Jekyll through the github-pages gem, which only allows a specific, short list of plugins: jekyll-feed, jekyll-seo-tag, jekyll-sitemap, jekyll-paginate, jekyll-gist, jekyll-avatar, jekyll-relative-links, and a handful of others. Anything outside that list in your Gemfile is silently ignored on Pages’ build, not an error, which is a confusing way to find out.
There is no whitelist at all if you build the site yourself and push the compiled output, or use a GitHub Actions workflow that runs jekyll build directly. Either removes the plugin restriction, at the cost of owning the build step.
Building yourself vs letting Pages build
Letting Pages build is zero-config: push Markdown and Pages runs Jekyll, with the plugin list above as the tradeoff. Building yourself with actions/jekyll-build-pages, or a plain workflow running bundle exec jekyll build and uploading the _site folder, works with any plugin and any Jekyll version, at the cost of maintaining the workflow file.
on:
push:
branches: [main]
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/jekyll-build-pages@v1
with:
source: ./
destination: ./_site
- uses: actions/upload-pages-artifact@v3
- uses: actions/deploy-pages@v4
Custom domain, and what a portfolio actually needs
A custom domain needs a CNAME file in the repository root containing the domain, plus DNS records at the registrar: an ALIAS or A record at the apex, or a CNAME at a subdomain, pointing at GitHub’s Pages infrastructure per GitHub’s own instructions.
Past the setup, a personal portfolio needs less than the theme galleries suggest: a short about section, three to six work samples with enough context to matter, and one clear way to get in touch. A comments widget, an analytics dashboard, and a blog nobody has posted to in two years add weight without adding to why someone visited.
How this fits the rest of the stack
Jekyll’s output is a folder of HTML whichever host serves it, so the site you build stays the same whether Pages hosts it or something else does. RunxBuild builds static sites from a repository with custom domains handled the same way, and the RunxBuild hosting calculator shows the cost once a portfolio’s traffic passes the included bandwidth.
Useful related references:
- GitHub.io Blog Template: Picking a Jekyll Theme, Wiring It Up, and Knowing When You Have Outgrown Pages
- Getting Started With GitHub Pages, and the Exact Point You Outgrow It
- Turn a GitHub Repo Into a Website: Four Ways, and Which One Fits
- Custom domains and certificates on RunxBuild
FAQ
Is Jekyll still a good choice for a personal website in 2026?
Yes, for a simple portfolio or blog. Its native GitHub Pages support means zero build configuration for a basic site, and the ecosystem of themes built specifically for academic and personal portfolios, like al-folio, is mature. It is a poor choice if the plan is a large content site or anything needing dynamic features Jekyll was never built for.
How do I fix Ruby install problems on macOS for Jekyll?
Avoid the system Ruby entirely. Install rbenv or asdf through Homebrew, install a current Ruby version under it, and run gem install jekyll bundler from that version. This avoids the permission errors and outdated Ruby version that come from using macOS’s built-in Ruby directly.
What plugins can I use if GitHub Pages builds my Jekyll site?
Only the plugins bundled with the github-pages gem, which includes jekyll-feed, jekyll-seo-tag, jekyll-sitemap, jekyll-paginate, jekyll-gist, jekyll-avatar, and a short list of others. Anything else in your Gemfile is ignored during a Pages-built deploy without an error message, so it is easy to miss.
Should I let GitHub Pages build my Jekyll site or build it myself?
Letting Pages build it is simpler and needs no workflow file, but restricts you to its plugin whitelist and pinned Jekyll version. Building it yourself with a GitHub Actions workflow removes both restrictions and is worth the extra setup as soon as you need a plugin that is not on the whitelist.
What does a personal portfolio site actually need?
An about section, a small number of work samples with real context rather than an exhaustive list, and one clear way to get in touch. A custom domain helps it look intentional. Comments, analytics dashboards, and an infrequently updated blog section add maintenance without adding much for a visitor.