jekylldown does for Jekyll what blogdown does for Hugo: create, build, preview, customize, migrate and publish Jekyll websites entirely from R, writing posts in R Markdown or Quarto.
The main benefits:
- The Jekyll theme ecosystem. First-class support for al-folio, the reference theme for academic sites: publications are generated from BibTeX and DOIs, and the CV, teaching and news pages are structured data. Chirpy, Minimal Mistakes and minima are also supported, and any other gem-based theme works with the same pipeline.
- You publish sources, not build artifacts. GitHub Pages runs
Jekyll natively and the bundled deploy workflows build the site in
101. The repository holds sources only: there is no
public/or_site/folder to commit and no rendered HTML to keep in sync. Publishing is agit push. - Nothing between R and Jekyll to break. Posts are knitted to plain Markdown, which Jekyll consumes directly; front matter, figures and tables are handled. Jekyll runs strictly through its command line, so there is no R-Ruby bridge and no version coupling.
- A complete toolchain from R, on every platform.
install_ruby()andinstall_git()set up an isolated toolchain, with no admin rights and nothing installed system-wide. The entire workflow runs on Linux and Windows CI on every push. - Migration from blogdown in one function call.
migrate_hugo()converts posts, page bundles, shortcodes, menu pages, site identity, social profiles and publications, and reports what needs manual attention. It has been validated page by page against real blogdown sites, including one with 1,884 posts. - Customization as code. Colors, fonts, semantic elements and
free-form CSS are set through idempotent, dark-mode-aware functions
(
set_theme_style(),set_theme_font(),set_element_style(),add_css()) instead of edits to theme files. - A quiet writing loop.
serve_site()runs in a background process and rebuilds incrementally on save. The console stays free.
jekylldown is not on CRAN yet. Install the development version with:
# install.packages("remotes")
remotes::install_github("allanvc/jekylldown")jekylldown needs Ruby (>= 3.0) with the jekyll and bundler
gems. Run jekylldown::check() at any time for a diagnostic of what is
(and is not) installed -- it looks both on your PATH and in
jekylldown's own isolated toolchain directory
(tools::R_user_dir("jekylldown", "data")).
No Ruby? You can still publish. Knitting
.Rmdposts only needs R. If you deploy on GitHub Pages/Actions (al-folio and Chirpy ship the workflow), the Jekyll build happens remotely and a local Jekyll is only needed forserve_site()previews and localbuild_site()builds.
To write posts in Quarto (.qmd) you also need the Quarto CLI --
install_quarto() puts it in the same isolated toolchain directory, or
it is picked up from the PATH.
One function call -- no admin rights, no graphical installer, nothing on
the system PATH:
install.packages("archive") # unpacks the portable Ruby archive
jekylldown::install_ruby()It downloads the portable RubyInstaller
archive into jekylldown's isolated toolchain directory, adds the MSYS2
build tools (Jekyll's own dependencies need a compiler on Windows -- a
large but one-time download) and installs the jekyll and bundler gems
there; deleting that one directory uninstalls everything. On networks
where the GitHub API is blocked, a known-good pinned release is fetched
directly from github.com; fully offline, download the .7z from
rubyinstaller.org and pass it as
file =. git is not required either: the GitHub-hosted theme templates
(al-folio, Chirpy) fall back to a plain archive download when git is
absent. The one step that needs git -- al-folio's bundle_install(),
whose Gemfile pulls a gem from a git repository -- is covered by
install_git(), which drops a portable MinGit into the same isolated
toolchain (for publishing with git push you will still want a regular
git installation). The whole workflow
-- install_ruby(), new_site(), build_site(), serve_site() --
runs on a Windows GitHub Actions runner on every push.
Performance tip: Jekyll is markedly slower on Windows, mostly
because real-time antivirus scanning intercepts every build's thousands
of small file reads. Excluding the site folder and the toolchain
directory from real-time scanning (Windows Security > Exclusions) makes
the single biggest difference; serve_site() also rebuilds
incrementally for content-only edits.
With admin rights (Debian/Ubuntu shown):
sudo apt install ruby-full build-essential
gem install --user-install jekyll bundler minimaWithout admin rights, use the fully isolated toolchain (conda-forge Ruby via micromamba, installed under jekylldown's data directory -- deleting that one directory uninstalls everything):
DATA=$(Rscript -e 'cat(tools::R_user_dir("jekylldown", "data"))')
mkdir -p "$DATA"
curl -Ls https://micro.mamba.pm/api/micromamba/linux-64/latest \
| tar -xj -C "$DATA" bin/micromamba
"$DATA/bin/micromamba" create -y -p "$DATA/ruby" \
-c conda-forge ruby c-compiler cxx-compiler make
PATH="$DATA/ruby/bin:$PATH" gem install --no-document jekyll bundler minimajekylldown auto-detects a toolchain in that directory and injects
GEM_HOME/PATH on every call -- no shell configuration needed.
The system Ruby that ships with macOS is outdated; install a current one with Homebrew (you also need the Xcode Command Line Tools for gems with native extensions):
xcode-select --install # if not already installed
brew install ruby
echo 'export PATH="$(brew --prefix ruby)/bin:$PATH"' >> ~/.zshrc && exec zsh
gem install --user-install jekyll bundler minimaPrefer a regular, system-wide Ruby to install_ruby()'s private copy?
Install RubyInstaller with
Devkit (the Devkit brings the
MSYS2 toolchain that compiles native gems), tick "Add Ruby executables
to your PATH" during setup and run the final ridk install step it
offers. Then, in a new terminal:
gem install jekyll bundler minimajekylldown uses whatever it finds on the PATH when nothing is
provisioned in its own toolchain directory.
jekylldown::check()The R commands are identical on Windows, Linux and macOS -- only the
one-time setup above differs per system. What changes per theme is one
extra step: themes that ship their own Gemfile need bundle_install()
once before the first build.
| Theme | Create | One-time extra step |
|---|---|---|
| minima (default) | new_site("my-site") |
none -- the theme gem is installed with the toolchain |
| al-folio | new_site("my-site", theme = "al-folio") |
bundle_install("my-site") -- needs git (on Windows, install_git()) |
| Chirpy | new_site("my-site", theme = "chirpy") |
bundle_install("my-site") |
| Minimal Mistakes | new_site("my-site", theme = "minimal-mistakes") |
bundle_install("my-site") |
From there the loop is the same everywhere and for every theme:
new_post("My title", dir = "my-site"), write, serve_site("my-site")
for the live preview (or build_site()), and publish (see Publishing
below).
Any other Jekyll theme works with the same pipeline: start from
new_site("my-site"), put the theme's gem in the Gemfile and in the
theme: line of _config.yml in place of minima, and run
bundle_install("my-site"). Posts in R Markdown/Quarto, new_post(),
build_site() and serve_site() work unchanged with any theme; the
theme-aware helpers (set_theme_style() palettes, set_theme_skin(),
the set_element_style() element maps, migrate_hugo() scaffolding)
are built for the four first-class themes above. Themes distributed only
as remote_theme for GitHub Pages have no local gem to build with: knit
with build_site(local_jekyll = FALSE) and let GitHub Pages run Jekyll
remotely.
library(jekylldown)
new_site("mysite") # scaffold a Jekyll site
new_post("My first post", dir = "mysite") # creates _source/YYYY-MM-DD-my-first-post.Rmd
build_site("mysite") # knit .Rmd -> .md, then `jekyll build`
serve_site("mysite") # live preview with rebuild on save
stop_server()new_site() also scaffolds theme = "al-folio", "chirpy" or
"minimal-mistakes". Posts come in three flavors: R Markdown via plain
knitr (default), R Markdown via pandoc (knit_method: pandoc in the
front matter -- enables citations, footnotes, cross-references), and
Quarto (new_post(format = "qmd")).
For a theme with its own Gemfile (like al-folio), install its gems
once with bundle_install("mysite"); build_site()/serve_site() then
switch to bundle exec jekyll automatically.
Declarative, idempotent, dark-mode aware -- no hand-written CSS in theme files. Four layers, most robust first:
# run from anywhere inside the site's project (they find the site root
# like build_site()); from outside, add dir = "mysite"
# 1. the theme's own CSS variables (al-folio and Chirpy)
set_theme_style(accent = "red",
background = c(light = "#fffdf7", dark = "#1c1c1d"))
# Minimal Mistakes styles through compiled skins instead:
set_theme_skin("dark")
# 2. fonts (Google Fonts inlined) and base size
set_theme_font("Lora", size = "17px")
# 3. one semantic element at a time (the fragile layer -- see its docs)
set_element_style("navbar", background = "#222", color = "white")
set_element_style("socials", size = "2rem") # social icon row
# 4. escape hatch: free-form CSS in a managed, removable block
add_css(".profile img { border-radius: 50%; }", id = "avatar")
# footer credit ("Built from R with jekylldown X.Y.Z."), added by
# new_site() and version-refreshed by build_site()
add_footer_credit() # remove_footer_credit() undoes itTo undo the accent, call set_theme_color(NULL); deleting the
site-local stylesheet (assets/css/main.scss) drops every customization
at once.
migrate_hugo("path/to/hugo-site", "my-jekyll-site", theme = "al-folio")migrate_hugo() converts posts (front matter, filenames, bundles,
static assets, common shortcodes), pages from the Hugo menu, the site
identity, social profiles and -- with publications = "bib" (the
default on al-folio) -- a publications page rebuilt from its DOIs, then
reports everything that needs manual attention. All four themes are
migration targets; each gets the pages, navigation, avatar and socials
in its own convention. See the vignette:
vignette("migrate-blogdown-to-al-folio", package = "jekylldown").
build_site() leaves a plain static site in _site/ -- so ultimately
any static host works. The knitting always happens locally (commit
_source/ and the generated _posts/*.md); what varies is who runs
jekyll build.
GitHub Pages (free, and Jekyll-native):
- Push the site to a repository --
youruser.github.iofor a user site, any name for a project site. - Get a build workflow. al-folio and Chirpy already ship one; for minima, Minimal Mistakes and other themes, add the standard one:
use_pages_workflow("mysite") # writes .github/workflows/jekyll.yml- Point Pages at the right source (once, in the repository settings)
-- the two bundled workflows publish differently:
- Chirpy's workflow and
use_pages_workflow()'s use the official Pages actions: set Pages > Source: GitHub Actions. - al-folio's workflow pushes the built site to a
gh-pagesbranch: set Pages > Source: Deploy from a branch > gh-pages / (root) (with "GitHub Actions" selected the workflow runs green but the site never updates). Every push then builds and publishes. Commit theGemfile.lockfrombundle_install()-- the workflows cache gems from it. One first-publication quirk: a force-push that replaces the branch history does not match the workflows'pathsfilters, so trigger the first run by hand (Actions > the workflow > Run workflow) or with any follow-up push.
- Chirpy's workflow and
Two alternatives on GitHub: the classic branch build (Settings > Pages
> Deploy from a branch) runs Jekyll for you but only with GitHub's
whitelisted plugin set -- fine for minima, not for al-folio/Chirpy; or
build locally and push the _site/ contents to a gh-pages branch if
you want no remote build at all.
Netlify / Cloudflare Pages: connect the repository and set the build
command to bundle exec jekyll build with publish directory _site
(both detect Jekyll and suggest exactly this).
GitLab Pages: a minimal .gitlab-ci.yml that runs bundle exec jekyll build -d public in a Ruby image and publishes the public
artifact.
Your own server / anything else: build_site() and copy _site/
over (rsync -av _site/ server:/var/www/site/). Static files, no
runtime.
| Where | What |
|---|---|
_source/*.Rmd, *.qmd |
posts in R Markdown/Quarto (the source; excluded from Jekyll) |
_posts/*.md |
knitted output -- an artifact, never edit by hand (unless the post has no _source/ twin: then it is the source) |
_pages/, _tabs/, or the root |
pages, in the active theme's convention -- edit directly; front matter controls the menu |
assets/img/posts/<post>/ |
figures generated by your chunks |
assets/.../main.scss |
site-local stylesheet holding the managed customization blocks |
Editing after creation or migration is covered in the getting-started
vignette (vignette("jekylldown"), section 5): where each theme keeps
pages, identity and data files, and why _config.yml edits apply live
under serve_site().
Prior art: this package packages up the workflow of Yihui Xie's
servr::jekyll() and
knitr-jekyll, with the API
mirroring blogdown on purpose. blogdown itself supports Jekyll only in a
limited way -- essentially the serve-and-reknit loop, with everything
else (site scaffolding, themes, toolchain, migration) designed for Hugo;
jekylldown picks up where that support stops.