Migrate WordPress to Jekyll
I had migrated this site LPRP.fr to WordPress in 2018 and I was very happy with it on many topics, but still, only 4 years after this migration I have decided to move to Jekyll.
Why?
I love WordPress on many aspects:
- Very simple to use
- Impressive community to get great plugins and themes
- Customizable easily in PHP
All these great features come with some drawbacks:
- I ended with more than 40 active plugins (and almost 10 inactive plugins for maintenance that I activate only when I need). Beside the generated complexity, it work quite well, but need frequent updates to avoid security exploits (almost 2-3 updates per day)
- Although I found some offline extraction techniques it did not perform well and was not really useable as offline site
- Although I installed many plugins to cache, minify, optimize, and added CloudFlare as CDN, it was much better, but still didn’t perform very well on speed tests like Lighthouse
- It requires hosting, without free / long-term options, meaning that the site will disappear if for some reasons I am not able to maintain it myself.
Install Jekyll
I will not explain much this part as it is well described in the official installation documentation
The only advice I can give is not to loose some time to use the ruby gems that may be included in your Linux distribution as you will run sooner or later in versions mismatch hell with the gems you will want to add. So just start with bundler as described in the documentation and everything will go smoothly.
On my debian:
sudo apt-get install ruby-full build-essential
gem install jekyll bundler
If you want later to update the gems you use, the command to use is:
bundler update
To create your site:
jekyll new myblog
cd myblog
bundle install --path vendor/bundle
# To build
bundle exec jekyll build --profile --trace
# To develop with autorefresh (and host exposition to be accessible outsite the localhost)
bundle exec jekyll serve --livereload --host 0.0.0.0
Migrate WordPress posts to Jekyll
I discovered only at the end of my migration that an official WordPress importer exists. So I won’t described that, but you definitely should look at it first.
The solution I used is a WordPress plugin called wordpress-to-jekyll-exporter. It will export all your posts and files in a format and directory structure suitable for Jekyll. If your WordPress is not small, it is likely the plugin won’t work if triggered by the admi panel, but you can run it by command line.
Just go inside the plugin folder (wp-contents/plugins/wordpress-to-jekyll-exporter
):
php jekyll-export-cli.php > jekyll-export.zip
Be sure to have a command line PHP installed (apt install php-cli
)
As this open source, it is very easy to modify it to fit exactly to your need, so I modified it to:
- added Polylang plugin extraction of languages (and reference to translated pages, and unifying translated categories and tags)
- added some processing to WordPress contents:
- remove the site host add by WordPress to get relative URI (useful to test on other hosts before switching)
- improved code section conversion, specially when using WordPress plugin crayon
- export image alignment in markdown styles
- convert spoiler tag to details HTML tag
- added some metadata and filtered useless ones from oceanwp WordPress theme (list is configurable)
- added comments export, with three destination possible:
- as data / collections (to be treated with some liquid)
- as posts with metadata and markdown as contents
- as GitHub discussions for use with Giscus
- and various settings to execute the script outside WordPress folders, to skip some parts of the script (useful to speed-up when running multiple times with posts or script modifications), and to generate without zip in a specific folder
I have published it on GitHub and proposed a PR.
Choose hosting mode
Before going further you must figure out what kind of hosting you want, as it will impact what is possible for themes and plugins. For now you have the choice between:
- Standard HTML hosting; no specific restrictions here, you will need to build your site with
jekyll build
command and serve the files oon a standard web server - GitHub built-in Jekyll hosting: you will only need to put your jekyll files in a GitHub repository and activate GitHub Pages and GitHub will take care of the rest and serve your files directly ; but this great ease comes with some limitations, as you only will be able to use themes and plugins whitelisted by GitHub (see GitHub documentation for list and details)
- GitHub Pages with actions: it is kind of the best of both world, the principle is to put your source jekyll files in a GitHub repository, and configure a GitHub action to build your site and publish it to GitHub Pages, and be able to use any theme or plugin you want. There is even a default GitHub action template to help. GitHub actions is for now free so that sound the best options at this time. And the one I have chosen.
Create layout
Then comes the time to choose and tweak the theme you will want to use. If you want to use GitHub built-in Jekyll hosting, the choice will be quite easy as there isn’t many options. If you can use any theme, there is tons of it and you can find the best places to search on the Jekyll themes page.
Personally I was quite happy with the current design of my WordPress blog, and choose to re-implement what was the result of the use of Ocean Wordpress customized theme, and many plugins.
Below was the screenshots of my WordPress site as of this migration:
There is not much to say about the layout creation, everything is quite well documented ; you will basically have to play with:
- The
_layouts
folder to define the main layout to use ; I created: default (common base), page (for pages), post (for posts), home-layout (for the home page), blog-layout (for the blog view), and archive (for auto-generated tags, categories and date pages generated by jekyll-archives plugin) - The
_includes
folder to define reuseable widgets between your layouts (I created: header, footer, menu, menu-item, main-loop-card, sidebar, toc, favicon, cookies-consent, comments ) - The
_sass
folder to define your stylesheet parts with the SASS language ease - The
assets
folder to define assets to be included (and the main.css that will include your saas parts)
There is no specific difficulties for this part for a frontend developed, what I am not. I got some help with Kevin Powell YouTube videos that are quite useful and inspiring for use of CSS.
Some main choices I made:
- Use modern CSS without bothering too much with compatibility with old browsers, as nowadays, it is quite crazy unconscious to use an old browser and all browser includes auto-update features to get with latest version and security patches. I know a few percents will have a not optimal browsing experience, but still will be able to view the contents, and this few people will get lower and lower with the years
- Use only standard CSS without specific addition from browsers (like
--webkit-*
and so on) to have less compatibility issues along the years - Full responsive design
- Use pagination and infinite scroll to get more loading time performance
The main features or my layout:
- All the basics: responsive, dynamic menu, responsive images,…
- Infinite scroll (on multiple pages, but with only one pagination as the jekyll-paginate plugin generates)
- Multi-languages support: link to translations, filter translated articles, redirect, Google Translate untranslated,…
- Modules activated by front-matter:
bootstrap
,jquery
,owl-carousel
,gallery
Content-Security-Policy
management (even still too muchunsafe-inline
used)- GDPR compliance (cookies consent, usage tracking with Piwik Pro that has been GDPR-compliant validated, and features like Google Translate activated only after consent to avoid personal data collected only with the inclusion of the javascript)
- Full-text search with javascript search loaded when needed (because we cannot process it server-side)
- Redirects to ensure URI compatibility over time (and yes, that is possible on GitHub Pages with a little bit of javascript, see below)
Of course as my site is on a public GitHub repository, you have access to the source of all above features if you want to see how it is done.
I took the opportunity to make some improvements o the Jekyll version:
- Blog view share the same design of card (one is vertical, the other horizontal)
- The whole site reactivity behave better, especially on high density screens
- There is a dark mode automatically selected depending on your browser settings
- Simplify the complex designs of RPhoto, xmlTreeNav and libxmldiff pages (from Elementor predefined layouts)
Review posts
Maybe the longest and boring part of the migration is the little tweaks to be done to the automatic conversion. Indeed at some point it is quicker to do make changes manually than implement full complexity in the converter PHP script to handle some few specific cases.
I intensively used git
for this part, as it is very useful to merge manual changes and new automatic conversion features. I use a export
branch for automatically exported posts with the PHP script, and the main
branch to make the manual changes. Merging the export branch in the main branch will keep the manual changes.
Markdown does not offer advanced styling possibilities, and that can be sometimes annoying for some simple things that centering an image, a disclaimer panel, a folding zone for extra details, etc.
But there is two good news:
- HTML is supported, so you can add extra style with HTML. Some markdown parsers allow a
markdown="1"
attribute to get markdown parsing inside the HTML. Be sure to leave one empty line between HTML and markdown. - Many markdown parsers allow to add styles or attributes to the generated markdown elements with
{: <additions> }
tags. To add a class, use{: .my-class }
, to add a id{: #my-id }
, to add any attribute{: my-attribute="my-attribute value" }
, and to you even can add inline custom CSS{: style="float: right;" }
.
I tried to restrict my markdown specificities as low as possible:
- the less HTML possible : there is no point using markdown to do HTML ; Jekyll can use HTML pages, so if you want to do HTML, use HTML… I use HTML for folding features with
<details markdown="1"><summary>title</summary> ... </details>
and frames for very specific posts. - the less styling possible, and to keep all the styles in one single SASS file to easily spot them
- for advanced features, the use of javascript libraries handled by the jekyll layout in the front-matter (as the gallery, the slider, the carousel,…)
Plugins
You may have guessed that I love extending possibilities to best fit my needs, and jekyll ist quite extensible for that. You have so much possibilities to extend things:
- Use code snippets / includes (see the Jekyll Codex for many useful resources without plugins)
- Use gems: there is some plugins lists like the one from Planet Jekyll GitHub or search GitHub with
jekyll-plugin
- Add plugins in
_plugins/
folder - Add javascript features to your layout
You will be able extend many things:
- Create new liquid tags or filter
- Generate new pages
- Alter content conversion
- Optimize the build speed
At the time of this article, the gems I use:
kramdown
as markdown converted withrouge
as source code highlighterjekyll-paginate
to create pagination for the post listsjekyll-sitemap
andjekyll-seo-tag
to generate sitemap and all the needed Search Engine Optimization (SEO) featuresjekyll-fontawesome-svg
to include the awesome fontawesome icons in your sitejekyll-archives
to generate pages for tags, categories, years and monthsjekyll-relative-links
to generate relative linksjekyll-admin
(which requireswebrick
) to have a nice GUI for editing articles (see below)jekyll_picture_tag
to generate responsive image sizes and formats (add needed dependencies withapt install libvips libvips-tools libwebp6
)jekyll-include-cache
to speed-up build time with cached include pages
I also have activated the gem liquid-c
to speed up build process.
And the custom plugins I use:
array_intersection.rb
(source) to add array_intersection filter that is very helpful for advanced liquid filtersfa_icon.rb
(adapted fromjekyll-fontawesome-svg
source code) to include svg without need offa_svg_generate
that is quite useful for cached include that won’t be processed byfa_svg_generate
include_absolute.rb
(source) to include contents for anywhere of your source files (include
liquid tags only search files in_includes
without the possibilities to navigate outside)md_link_relative.rb
(adapted from this) to make markdown links work with baseurl (I don’t understand why this is not in the standard Jekyll behavior…)protect_code.rb
to avoid conflicts with liquid tags in code blocksreading_time_filter.rb
(source) to add reading time to post meta datarelated_posts.rb
(adapted from this) to get better related_posts based on tags and categories (the default one is only the latest posts, LSI is so slow, and give not really relevant results) ; I have also a liquid only version adapted from Jekyll Codex, but this should be faster.
For decent build time I suggest you think twice before using complex liquid algorithms or plugins that generates pages as it will grow your build time. To reduce the build time, some advice:
- Use
bundle exec jekyll build --profile
to see what is time expansive and you should focus on optimizing - Use
jekyll-include-cache
; that may need a little rework to get all variables in include scope, but it is really very effective to speed up things - Try to reduce and split the page-specific part of widget or menus to benefit more from cache
- Use
jekyll serve --incremental
when developing to get faster updates - Use
liquid-c
gem
Add commenting system and redirects
The comment system is maybe the part that needed the most thinking, and still I don’t have found the perfect solution. The requirements source of the dilemma:
- It is quite odd to want dynamic comments with a static site… but yes, comments are useful to get feedback and help readers with unclear parts of the article
- You don’t want leaving a comment to be too difficult for users, but you also must anticipate that there will be a lot of spam, so systems based on e-mails must be avoided and user accounts are quite inevitable. Systems like wordpress comment system without accounts but with anti-spam from akismet is the smartest solution, bt I did not found an equivalent
- There isn’t hosted commenting systems with free plans without advertising
- There is quite good free self-hosted system, but it is not very consistent to strive to have your site non self-hosted and self-host your commenting system
But having decided to host my site on GitHub, there are a few solutions that use issues or discussions to handle comments. The one that seemed to me the most convincing at this time is Giscus that will store the comments as replies to a discussion that correspond to the page/post. Giscus offers also a way to embed all that very nicely in your page, and it is free. This come with some drawbacks: commenting will require that user have a GitHub account (quite easy to create on GitHub, and shouldn’t be a problem for my readers given the topics I cover on this site), and to be able to embed nicely, it requires the use of the Giscus app ; I hope it will keep online and free on the long term.
Setting up Giscus is very easy as there is a form to generate the code to embed in your site with the options you want on Giscus
I had a small problem with current version that does not have yet a good basepath support, so I made this Pull Request
Giscus does not come with a system to migrate existing comments to GitHub discussions, and that is quite logical in a way because it cannot impersonate the original authors of the comments, but still I wanted to keep history. There is also the option to include statically the WordPress comments, but it wouldn’t have had the same design as newer, and it will not be shown in the GitHub discussion.
So I figured out a way to migrate comments by adapting the wordpress-to-jekyll-exporter
PHP plugin to export comments, and make some GraphQL calls to GitHub to create the comments as discussion replies. I will make soon a dedicated post to cover that topic.
Author with jekyll-admin and _config_dev.yml
I must say that WordPress has a very nice authoring experience, and that I am not a full command line addict guy… VSCode is a quite decent editor for markdown articles to be included in a Jekyll site with code spell and other plugins, but I was quite happy to discover the gem jekyll-admin
which offer a decent editor interface to edit your markdown posts.
It is very simple to set up:
- Add the
jekyll-admin
gem - Serve your jekyll site with
bundle exec jekyll serve --livereload
- Navigate on
/admin
(eg http://localhost:4000/admin)
Be sure to secure or not expose this path.
You may also want to have specific settings for your development experience. To avoid to duplicate your _config.yml
you can create a specific one that will be merged after:
- Create
_config_dev.yml
# Override settings for dev mode title: '[dev] LPRP.fr' # Remove generation of archives in development to speed up things jekyll-archives: enabled: [] show_drafts: true jekyll_admin: homepage: drafts
- Run with
bundle exec jekyll serve --livereload --incremental --drafts --profile --config "_config.yml,_config_dev.yml" --host 0.0.0.0
Also if you run your site with a base path (the case on GitHub if you don’t use the default user repository), you will need:
- to be sure that all your layouts and includes take care of adding
{{ site.baseurl }}
in front of every path, or use the filter| relative_url
(avoid the latter in cached includes as it won’t work if all your pages are not on the same folder structure) - to append
-b <your-base-path>
to your build or serve command
jekyll-admin
is already quite handy to use, but I added some improvments to get an interface I find more productive for myself:
- real preview on the side (refreshed automatically if you activate live-reload)
- choice of other Markdown editors (TUI, TinyMDE, basic textarea or original SimpleMDE) to benefit from spell checkers like LangageTool that do not work with SimpleMDE which is for now the one used by jekyll-admin
- header_buttons settings to add custom URLs as buttons in the header to have all your ressources to edit webpages at your fingertips
- force_show_drafts settings to force drafts when using multiple _config.yml or –drafts command line
- new_meta_defaults settings to add automatically predefined meta on new drafts (and avoid forgetting to set some of it)
- sort static files index by most recent files
Comparing Lighthouse speed tests
At this time of reading, due to the size of this post you may have forgotten why we run into this process, and a part of it was speed performance. So let’s see the result of the Lighthouse scores.
Lighthouse WordPress Mobile scores | Lighthouse Jekyll Mobile scores |
---|---|
Lighthouse WordPress Desktop scores | Lighthouse Jekyll Desktop scores |
Three months later I can observe I almost doubled the traffic, with no specific reason, so I guess the performance improvements have a lot to do with it.
More?
As this post is already very long, and that some topics may be useful outside my personal migration process, some more articles to zoom on these topics:
- Redirects for GitHub Pages and for WordPress direct page links
- Migrate WordPress comments to Giscus
- Multi-language Jekyll
And also, my website repository is available on GitHub, so feel free to look and borrow code on other special features included:
- GDPR compliance
- Full text search in client side javascript
- Infinite scroll
- Modular javascript to avoid loading useless code
- gallery / slider support
- some custom plugins:
- better related posts
- liquid code protection
- reading time filter
- relative links for markdown