Move a Jekyll Site into Hugo with hugo import
Your blog has lived on Jekyll for years and rewriting it by hand is not happening, so hugo import jekyll does the heavy lifting instead. You will finish with a Hugo project containing converted posts and a generated configuration file, while the original Jekyll tree stays untouched. The examples use Hugo 0.123.7, installed here from the Ubuntu package. Allow 15 to 30 minutes for a small site, plus whatever time you spend checking front matter, links and theme behaviour afterwards.
The route
Jump straight to the step you need, or tick off Done means at the end.
You need a shell, read access to the Jekyll site, and a destination path Hugo may write to. The importer is a migration aid, not a full site conversion: it will not choose a theme for you, and a clean exit status is not proof that the rendered site matches the old one.
1. Confirm the installed command
Start with a read-only version check; it needs no elevated privileges:
$ hugo version
hugo v0.123.7+extended linux/amd64 BuildDate=2026-03-17T19:51:14Z VendorInfo=ubuntu:0.123.7-1ubuntu0.3+esm2
Your build date or vendor suffix may differ. The Hugo version is the detail that matters, since import output can change between releases. Confirm the subcommand's contract too:
$ hugo import jekyll --help
hugo import from Jekyll.
Import from Jekyll requires two paths, e.g. `hugo import jekyll jekyll_root_path target_path`.
Checkpoint
The full command is hugo import jekyll JEKYLL_ROOT HUGO_TARGET. The first path is the existing Jekyll root. The second is the directory Hugo will populate.
2. Protect the source and choose a new target
Back up or commit the Jekyll site before migrating it. The import itself only reads from the source and writes to the target, but a backup gives you a known recovery point if the converted content needs manual repair. Never make the target the Jekyll root itself.
For a clean first pass, create a new empty directory. This ordinary command changes only the directory you name:
$ mkdir -p "$HOME/migrations/my-site-hugo"
$ find "$HOME/migrations/my-site-hugo" -mindepth 1 -maxdepth 1 -print
Expect no output. Replace /path/to/jekyll-site and the target path below with real paths, and quote both so spaces do not split them into extra arguments.
Resist sudo as a first response to a permissions error. Check ownership and permissions, then pick a writable target or fix access deliberately; importing as root can leave generated files owned by root and make later editing awkward.
3. Run the import into the empty directory
Run the importer as your normal user:
$ hugo import jekyll \
"/path/to/jekyll-site" \
"$HOME/migrations/my-site-hugo"
Import Jekyll from: /path/to/jekyll-site to: /home/you/migrations/my-site-hugo
Importing...
Converting .../_posts/2026-01-02-hello-import.md
2026-01-02-hello-import.md 2026-01-02 00:00:00 +0000 UTC hello-import
Congratulations! 1 post(s) imported!
The exact file list, timestamps and post count depend on your source. A successful run also prints suggested next steps involving Git, a theme and hugo server; those are suggestions, not something the importer runs for you.
Checkpoint
Require exit status 0 and a success message before inspecting the target:
$ printf 'import status: %s\n' "$?"
import status: 0
4. Inspect what Hugo created
List the target before opening it in an editor:
$ find "$HOME/migrations/my-site-hugo" -maxdepth 3 -type f -print
/home/you/migrations/my-site-hugo/hugo.yaml
/home/you/migrations/my-site-hugo/content/post/2026-01-02-hello-import.md
The importer writes hugo.yaml and converts Jekyll posts into Markdown under content/post. In this tested example, the generated configuration carries values such as baseURL: http://example.org/, disablePathToLower: true, a language code and the source title. Treat http://example.org/ as a placeholder and replace it with your real base URL before publishing.
Open the generated front matter next to the original Jekyll post. Check dates, titles, drafts, categories, tags, permalinks and any custom fields your templates rely on. Hugo may have no equivalent for every Jekyll convention, so keep the original tree available until these comparisons are finished.
5. Handle a target that is not empty
By default, Hugo refuses to import into a non-empty target, which protects an existing project from an accidental merge:
$ hugo import jekyll \
"/path/to/jekyll-site" \
"/path/to/existing-hugo-site"
Error: command error: target path "/path/to/existing-hugo-site" exists and is not empty
That failure leaves the target untouched for inspection. If it holds files you need, do not reach for --force casually; back it up, record the changes, and decide which project owns each path first.
Use --force only once you have deliberately chosen a merge into that directory:
$ hugo import jekyll --force \
"/path/to/jekyll-site" \
"/path/to/existing-hugo-site"
Importing...
Congratulations! 1 post(s) imported!
Warning
This changes state. Files with matching names may be replaced or combined depending on the importer and the source's contents. Before using it on a working project, make a separate copy or commit so you can restore the previous target. Recovery means removing or renaming the experimental target and restoring that copy or commit; never delete the only copy of either site.
6. Build and review the converted site
The import command does not install a theme, initialise Git or start a server. Follow your normal project setup, then build from the target:
$ cd "$HOME/migrations/my-site-hugo"
$ hugo
Start building sites ...
| EN
------------------+----
Pages | ...
The summary varies with content and configuration. A non-zero status is a migration issue to fix before serving or deploying anything. Review rendered URLs, navigation, images, code blocks, dates and draft visibility, comparing a representative set of old and new pages rather than assuming matching filenames mean matching output.
For an interactive look, run hugo server only from the converted copy, and stop it with Ctrl-C when you are done. Do not point a deployment job or a production web root at the directory until the generated content and configuration have passed your normal review.
Done means
- Confirmed the version and syntax. You checked the installed Hugo version and the two positional import paths.
- Kept the source safe. The original Jekyll tree remains available as a backup or committed source.
- Imported cleanly. The command completed with exit status 0 into a deliberately chosen target.
- Reviewed the output. You inspected
hugo.yamland the converted files undercontent/post. - Fixed placeholders. Values such as
http://example.org/were replaced before publishing. - Used force sparingly.
--forcewas only used with a recoverable target, and the resulting build was reviewed.