Gohyde reads the same _config.yml, front matter, layouts, includes, and
collections as Jekyll, so most sites build with little or no change. This guide
covers the friction points that show up on real sites.
cd my-jekyll-site
gohyde build
rm -rf _site _builds.filter == true-style conditions).defaults, permalinks (date/pretty/ordinal/none/custom)._posts), pagination, data files.{% include %} with parameters and runtime values.url and baseurlAsset URLs and page URLs include baseurl. Make sure absolute links prepend
{{ site.url }} explicitly:
<link rel="canonical" href="{{ site.url }}{{ site.baseurl }}{{ page.url }}" />
If site.url is blank, links render relative — uncomment url: in _config.yml
for absolute URLs.
Install Dart Sass (npm i -g sass or your package manager). The legacy Ruby
sass gem is detected and skipped. Without a real compiler, SCSS passes through
unprocessed with a warning.
jekyll-assets-style tags{% asset %}, {% javascript %}, {% stylesheet %}, {% image %} and the
@path/@url/@uri URL-only flags are supported. See Assets.
Ruby/Python Jekyll plugins don’t run as-is — port them to Gohyde’s plugin
SDK, in whichever of the three runtimes fits best (see Writing
Plugins): Ruby or Python if you’re porting
existing Ruby/Python logic largely as-is, Go if you want the fastest
option (in-process, no subprocess/JSON-RPC per call) and don’t mind
compiling a .so — the same hook/filter/tag/block-tag/converter/generator
surface is identical across all three, so nothing is Go-only or
Python/Ruby-only.
gohyde migrate --fix will attempt to auto-port Ruby Liquid filters (a
plugin that reopens Jekyll::Filters, or passes a module to
Liquid::Template.register_filter) into a <name>.gohyde.rb file next to
the original — this is the one Jekyll plugin idiom mechanical enough to
translate automatically. This is a best-effort convenience, not a
guarantee — always review the generated file before trusting it in a real
build; a method body copied verbatim from Jekyll may reference helpers or
gems that don’t exist in Gohyde’s plugin process. Everything else
(Jekyll::Generator subclasses, Jekyll::Hooks.register callbacks, custom
Liquid::Tag classes, anything touching Jekyll internals) has no automatic
equivalent and is reported, not guessed at — port those by hand using the
idioms below.
Three Ruby idioms that differ:
Liquid::Template.register_tag → the tag DSL — but check the base class first. A Jekyll tag class registers the SAME way whether it subclasses Liquid::Tag or Liquid::Block, so gohyde migrate flagging this call doesn’t by itself tell you which Gohyde primitive to use:
class Foo < Liquid::Tag (no body, e.g. {% my_tag args %}) → tag :name do |args, ctx| … end.class Foo < Liquid::Block (HAS a body, e.g. {% my_tag args %}...{% endmy_tag %}) → block_tag :name do |args, content, ctx| … end instead. tag has no way to see a tag’s body at all — using it for a Liquid::Block-based plugin silently produces a broken port, not an error. gohyde migrate flags a Liquid::Block subclass with its own, separate suggestion pointing at block_tag specifically — see examples/plugins/exclude_in.rb (and its Python/Go twins, exclude_in.py/exclude-in-go) for a real ported example.ctx is {"page" => ..., "site" => ...} — the SAME live scope real Jekyll’s context.registers[:site]/context.registers[:page] would give a Liquid::Tag/Liquid::Block subclass’s own render(context) method.return inside a block raises LocalJumpError. Jekyll tag code that does
an early return inside an iterator must be rewritten. Use find / .lazy
instead of .each + return:
# Jekyll plugin (won't work as a Gohyde tag block):
pages.each { |p| return p.url if p.slug == target }
# Gohyde tag block:
tag :entrylink do |args|
target = args.strip
page = @pages.find { |p| p["slug"] == target }
page ? page["url"] : ""
end
For nested searches, .lazy.flat_map { … }.first avoids the early-return
problem entirely.
jekyll-feed, jekyll-seo-tag, etc.Gem-based tag plugins ({% feed_meta %}, {% seo %}) aren’t bundled. Inline the
markup or port the plugin:
<link type="application/atom+xml" rel="alternate"
href="{{ site.url }}{{ site.baseurl }}/feed.xml"
title="{{ site.title }}" />
Jekyll::Converters::Markdown::*, e.g. kramdown_pygments.rb)A widely-copied Jekyll snippet swaps the site’s default Markdown processor
for kramdown+Pygments instead of kramdown+coderay/Rouge, selected via
_config.yml’s markdown: key. Gohyde’s own markdown: key is parsed but
ignored — Gohyde always renders through goldmark (see
Configuration) — so there’s nothing to plug a
custom converter class into at all. This isn’t a porting gap: delete the
plugin file. What it’s usually for (real, per-token syntax highlighting
instead of a bare unstyled <pre>) is already covered natively — see
Liquid & Jekyll compatibility on Chroma. Dropping the
Ruby file in as-is (rather than deleting it) won’t crash the build either
way: it never calls the Gohyde SDK’s Gohyde.run, so the Ruby bridge just
reports “plugin closed stdout” as a warning and moves on.
{% include %}A common technique: copy Jekyll’s own lib/jekyll/tags/include.rb
(IncludeContentTag/IncludeContentRelativeTag) and point it at a
different directory (e.g. _localincludes/ instead of _includes/) via a
renamed tag like {% localinclude %}. Don’t port this — Gohyde’s own
{% include %} already supports subdirectories under _includes/:
move the files into e.g. _includes/local/ and change your templates from
{% localinclude thing.html %} to {% include local/thing.html %}. The
underlying Jekyll::Site internals this kind of plugin depends on
(includes_load_paths, liquid_renderer, regenerator, registers[: cached_partials]) have no Gohyde plugin-context equivalent anyway, so a
manual port isn’t a realistic fallback even if you wanted one.
Run the same site through both generators and diff the output:
jekyll build -d _jekyll_out
gohyde build -d _gohyde_out
diff -r _jekyll_out _gohyde_out
Whitespace and attribute ordering may differ; semantic differences are bugs — Jekyll’s output is the spec.