Switching from Jekyll to VitePress

2 Dec 25

devweb

This site has run on Jekyll since 2014 (or even before, I think; but some secrets are lost to time). Ten years is a good run for any technology choice, but I’ve finally made the switch to VitePress. The source is open, so you can check it out for yourself.

Jekyll still works fine for pure markdown→HTML, and so-called “content sites”. The problem is everything around the edges, and especially things which use packages from npm. My site has accumulated interactive components, reveal.js slide decks, custom syntax highlighting for obscure languages, and enough Ruby plugins that I’d forgotten what half of them did. And while Jekyll is mature, it does seem like it’s kindof in maintenance mode these days.

I’d been using VitePress for a few other projects and kept being impressed by how much you get out of the box: Vue components and TypeScript, with a lightning-fast developer experience. Hot module replacement actually works, which is something Jekyll’s --livereload never quite managed reliably.

But the real appeal is that the escape hatches are sensible. When you need custom behaviour, you write TypeScript or Vue instead of Ruby plugins and Liquid templates. The mental model is cleaner: it’s just Vite with some markdown conventions.1

If you’re gonna make the same move, here are a few things to note. Honestly, just point Claude at this blog post, because this sort of thing (moving from one well-known stack to another) is the sort of thing that LLMs shine at. The main shifts come down to four things: content structure, custom components, reveal.js slides, and data loading.

Jekyll wants posts in _posts/YYYY-MM-DD-slug.md. VitePress doesn’t care, so I reorganised into blog/YYYY/MM/DD/slug.md. This preserves my existing URLs while making the directory structure more navigable. Cool URIs don’t change.

The trick is extracting dates from the URL path rather than requiring them in frontmatter. VitePress lets you do this with transformPageData:

transformPageData(pageData) {
  const dateMatch = pageData.relativePath.match(
    /blog\/(\d{4})\/(\d{2})\/(\d{2})/
  )
  if (dateMatch) {
    const [, year, month, day] = dateMatch
    pageData.frontmatter.date = `${year}-${month}-${day}`
  }
}

For custom components, all my Jekyll includes became Vue components. A YouTube embed that was 20 lines of Liquid with magic width calculations became:

<template>
  <div class="youtube-wrapper">
    <iframe :src="embedUrl" allowfullscreen />
  </div>
</template>

At a certiain level of complexity, having a proper programming language (not just a templating system) becomes worth the tradeoffs. The component is type-safe, composable, and I can actually read it six months later.

Reveal.js slides were the fiddly bit.

Info

Honestly, the reveal.js stuff is still a work in progress, and I’m not super happy with it just yet.

I had a custom Jekyll plugin that transformed markdown into reveal.js slides. I rewrote this as a markdown-it plugin that runs at build time, splitting content on h1/h2 headers and hoisting data-* attributes onto the reveal sections.

Data loading rounds out the list. Jekyll has _data files that become available in templates. VitePress has .data.ts files that can do the same thing—but with TypeScript, so you can parse BibTeX files, fetch from APIs, or do whatever processing you need:

export default {
  load() {
    const publications = parseBibtex("_data/ben-pubs.bib");
    return publications.sort((a, b) => b.year - a.year);
  },
};

The data is computed at build time and available in your pages. No runtime overhead.

In the wash-up, what I like about the new setup comes down to four things. The dev experience is dramatically better, with instant hot reload and actual error messages instead of Liquid stack traces. Type safety is end-to-end across configuration, data loaders and components, so I catch errors at build time instead of discovering them in production. The component model is a better abstraction than Liquid includes; state, props, and slots map cleanly to what you’re actually trying to do. And the production build is smaller and faster, though honestly this was never really a problem, since static sites are fast by definition.

Should you switch? If your Jekyll site is just markdown and you’re happy with it, stay put. Jekyll still does that well.

But if you’re fighting Liquid templates, maintaining Ruby plugins you don’t understand, or wanting to add interactive components without a whole separate build system, VitePress is worth considering. The migration isn’t trivial, but it’s straightforward if you take it piece by piece.

The old Jekyll site is preserved on the jekyll tag if you want to compare. Or browse the current main branch on GitHub to see how it all fits together.

If there’s something which was on the old site that you can’t find on the new one, then drop me a line and I’ll try to help you find it.

#Footnotes

  1. If you’re not familiar with Vite, it’s the modern build tool that replaced webpack in most of my projects. Fast, and also other things, but mostly fast.

Cite this post
@online{swift2025switchingFromJekyllToVitepress,
  author = {Ben Swift},
  title = {Switching from Jekyll to VitePress},
  url = {https://benswift.me/blog/2025/12/02/switching-from-jekyll-to-vitepress/},
  year = {2025},
  month = {12},
  note = {AT-URI: at://did:plc:tevykrhi4kibtsipzci76d76/site.standard.document/2025-12-02-switching-from-jekyll-to-vitepress},
}