{"id":37857752,"url":"https://github.com/amanessinger/wordpress-xml-to-hugo","last_synced_at":"2026-01-16T16:29:07.733Z","repository":{"id":57555174,"uuid":"142678310","full_name":"amanessinger/wordpress-xml-to-hugo","owner":"amanessinger","description":"A tool for converting my blog https://manessinger.com to Hugo.","archived":false,"fork":false,"pushed_at":"2018-08-15T16:34:56.000Z","size":39,"stargazers_count":3,"open_issues_count":0,"forks_count":5,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-11-15T12:07:20.563Z","etag":null,"topics":["golang","hugo","migration","wordpress"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/amanessinger.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-07-28T13:11:11.000Z","updated_at":"2021-07-10T11:52:33.000Z","dependencies_parsed_at":"2022-09-26T18:51:37.771Z","dependency_job_id":null,"html_url":"https://github.com/amanessinger/wordpress-xml-to-hugo","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/amanessinger/wordpress-xml-to-hugo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amanessinger%2Fwordpress-xml-to-hugo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amanessinger%2Fwordpress-xml-to-hugo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amanessinger%2Fwordpress-xml-to-hugo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amanessinger%2Fwordpress-xml-to-hugo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/amanessinger","download_url":"https://codeload.github.com/amanessinger/wordpress-xml-to-hugo/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amanessinger%2Fwordpress-xml-to-hugo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28479884,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T11:59:17.896Z","status":"ssl_error","status_checked_at":"2026-01-16T11:55:55.838Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golang","hugo","migration","wordpress"],"created_at":"2026-01-16T16:29:07.006Z","updated_at":"2026-01-16T16:29:07.723Z","avatar_url":"https://github.com/amanessinger.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# wordpress-xml-to-hugo\n\n## Introduction\n\nThis is an attempt at an opinionated migration from WordPress to\n[Hugo](https://hugo.io/). The migration is based on parsing a blog's\nXML export using a parser based on \n[WordPress XML Parser](https://github.com/grokify/wordpress-xml-go).\n\nThe original parser ignores comments, therefore first\n[a fork wss used](https://github.com/amanessinger/wordpress-xml-go),\nand finally the parser was incorporated into this project. \n\nThe goal is, to migrate to a completely static blog, that can be \ndeployed to the free tier of [Netlify](https://www.netlify.com/).\n\nNo data about visitors will be collected, no third-party tools will\nbe used.\n\nThe project is motivated by the intention to migrate my \nlong-standing blog from WordPress to a static site generator.\n\nIf you're interested to use this tool, I'll explain how at the end \nof this text. Skip my explanations if you will, but I strongly\nadvise you to read through once. I'll explain my options and choices,\nand they may turn out to be different from yours.\n\n## Rationale for the choice of tools\n\nLast time I checked (mid-June 2018), \n[StaticGen](https://www.staticgen.com/) listed 224 options for \nstatic generators. Maybe a third is not intended to be used for blogs,\nbut this still leaves more than I was willing to try. \n\n* The initial idea was, to select a tool that's written in one of the\n  languages I have been working with recently. This would be\n  JavaScript, TypeScript, Python or Java. Exotic languages like Haskell\n  or Erlang were ruled out, legacy languages like Perl were deemed too\n  old to base a new system on.\n\n* The next criterion was popularity. A tool should have a considerable\n  active user base.\n\n* The final initial selection criterion was the existence of a \n  migration tool.\n\nIn June 2018 the top five on [StaticGen](https://www.staticgen.com/)\nwere Jekyll (Ruby), Next (JavaScript), Hugo (Go), Gatsby (JavaScript)\nand Hexo (JavaScript). Next and Gatsby are more intended for building\nReact applications, Jekyll and Hexo have a migration tool. As I\nwas neither proficient in Ruby nor Go, Hexo was a clear choice\nfor first experiments.\n\nIt didn't end well. Hexo had no problem migrating 4300 posts to\nMarkdown, but upon rendering, it didn't even manage to load all those\nposts. Without generating a single HTML file, it crashed after an \nhour with out-of-memory.\n\nTo be fair, the test was executed in a RHEL 7 VM with only one\nCPU and 1 GB of memory. Hexo might very likely have fared better \nwhen running directly on my MacBook Pro, but nevertheless,\nthe bad impression was there. \n\nHugo was supposed to be the speed king when rendering. In order to \nget a feeling of what can possibly be expected, I installed\nHugo (one binary only) directly on the Mac. Hugo rendered the whole\nblog in maybe 10 seconds and the result was already a blog, linking,\ncategories and tags included. It also looked gorgeous. What should\nhave been an experiment, solely for orientation, turned out to be \na mighty incentive for reconsidering the selection criteria.\n\nThis time the approach was backward. [Hugo](https://hugo.io/) had to\nbe the winner. The idea now was, to find out if there was a viable\nmigration path.\n\n* As to the language [Go](https://golang.org/), there had already been\n  interest in learning it. I had only lacked a good reason to\n  do so. Therefore not knowing the language turned out to be\n  a welcome excuse to learn it.\n\n* Hugo has a converter based on a WordPress plugin, but it did not\n  produce the expected results of my blog. The Hugo docs\n  recommend the Jekyll converter as a fallback, but although the\n  preservation of HTML produced results pleasingly similar to the \n  original layouts, the blog structure was not properly preserved.\n  Actually now I know that this was my fault. Hugo themes are either\n  written to expect posts under \"content/post\" or under \n  \"content/posts\". If you apply the wrong theme to a your chosen\n  structure, no posts are found. Anyway. There were other gripes\n  as well, for instance the choice of file name and project \n  structure. 4300 posts in one directory, that's not very usable.\n\n* The Jekyll converter did extract comments (which Hexo had not), \n  but they were put into the Markdown file's front matter and were \n  ignored by Hugo\n\nIn any case the experiments indicated that some kind of custom \nconversion would be necessary. \n\nThe markdown files out of Jekyll's converter did contain the necessary \ninformation, but they would need extensive post-processing. \nInstead of going that route, I decided to completely do it \nmyself by parsing an XML export out of WordPress.\n\nIn order to learn [Go](https://golang.org/), I chose it as \nimplementation language. \n[WordPress XML Parser](https://github.com/grokify/wordpress-xml-go)\nwas chosen as a good starting point.\n\n## Requirements\n\n* The main purpose is to port my photo blog \n  [manessinger.com](https://manessinger.com/) over to a git-based \n  static workflow. This is, what the tool will be optimized for. If \n  it can be generalized for broader applicability, that's fine. No\n  compromises will be made in this regard though.\n  \n* The blog [manessinger.com](https://manessinger.com/) had 4300 posts \n  in August 2018. The blog has had about one post per day since \n  fall 2006. Posts with more than one image always had one big,\n  centered image right after the title, followed by text intermixed \n  with thumbnails of the other images. A good example is\n  [here](https://manessinger.com/2018/05/4214-the-congress-hall-from-afar-v.html). This layout\n  can't reasonably be changed, thus the migration needs to favor \n  preservation of HTML over clean, uncluttered Markdown. I may \n  write proper Markdown in the future though.\n\n* In August 2018 the blog had 5852 approved comments. Preserving \n  these comments and their thread structure is important.\n\n* Commenting must be possible and replying to existing comments as \n  well. \n\n* In the interest of rendering speed, no helper applications will be \n  used. Rendering will be done entirely by Hugo. This alone rules\n  out \n  [reStructuredText](https://en.wikipedia.org/wiki/ReStructuredText),\n  a format that would have allowed me to get rid of HTML tags and\n  still be able to center or align images. The problem is, that \n  although Hugo supports reStructuredText, it does so by firing\n  up a Python script **for each post**! Multiply that by 4300 and\n  you know why I dislike the idea. Should Hugo ever get proper\n  in-process support of reStructuredText, I may start using it. On \n  the other hand, reStructuredText is just another language that I\n  would have to learn, and it is a language that I use for nothing\n  else. If you look at it this way, using Markdown with embedded\n  HTML is probably the best choice for me.\n\n## Handling of comments\n\nThis is where it gets opinionated :)\n\n### Options\n\nThe most common strategy for providing comments on static sites seems\nto be, to employ a third-party service like \n[Disqus](https://disqus.com/). This is problematic for \nseveral reasons:\n\n* Those services slow down an otherwise blindingly fast site. To be\n  clear, the pages can be structured to display static content\n  immediately, but there is still the feeling of slowness when one\n  watches the comments loading.\n\n* Usage of those sites is adversarial to the spirit of the \n  [GDPR](https://en.wikipedia.org/wiki/General_Data_Protection_Regulation).\n\n* Ad-free comments are a feature of paid services only. Disqus does\n  offer an ad-free service for non-commercial sites running no ads \n  themselves, but unfortunately \n  [manessinger.com](https://manessinger.com/) ran Amazon ads for\n  years. This was done as a way to link to music, and although the\n  ads never generated income worth talking about, the code is \n  wrought into the pages. I chose to filter the ads out during \n  conversion, so theoretically I could use Disqus for free. I\n  still don't like it though, because of the last point:\n\n* Migration of existing comments to such a system may likely not be\n  possible, because they all rely on the users having accounts with \n  them. \n\nFor all these reasons the comments will be rendered into the pages.\nIn case of existing comments this seems to be the only viable option,\nand for new comments it will need a submit/approve/merge mechanism.\n\nSubmission will be through a \n[Web Component](https://en.wikipedia.org/wiki/Web_Components) \ncreated with [Stencil](https://stenciljs.com/). The component will\nonly be loaded upon click on a comment \"button\" rendered via SVG.\nThis should eliminate all SPAM submitted by bots not running in\na browser.\n\nThe Web Component would present a form and it would submit the \ncomment to a serverless function. This could be anything, and the\nobvious option is AWS Lambda, because the free tier of \n[Netlify](https://www.netlify.com/) already includes 125.000 free\ninvocations per month. This should easily cover real comments and\nall SPAM possibly entered by humans.\n\nAs to SPAM filtering, WordPress uses (and provides for third \nparties to use) the Akismet service. In years of usage, Akismet has\ndemonstrated excellent selectivity and high reliability. It is only\nnot easily compatible with the \n[GDPR](https://en.wikipedia.org/wiki/General_Data_Protection_Regulation)\nThere are indeed doubts as to its legality at all.\n\nTechnically Akismet could easily be integrated into any comment \nsubmission mechanism, so its usage has not entirely been ruled\nout for my purposes. Still, if an alternative solution \ncan be found, that would be strongly preferred.\n\nOne such alternative could be based upon the algorithms implemented\nin [Antispam Bee](https://github.com/pluginkollektiv/antispam-bee).\n\nExecution of antispam code would already be in a serverless function.\nThe outcome would be either rejection or, hopefully, submission\nto me via email. In low-volume situations like mine, \nthis might already suffice. I would merge the new\ncomment into the appropriate file and that would be it.\n\nFor higher-volume situations like that of for instance Mike\nJohnston's of \n[The Online Photographer](http://www.theonlinephotographer.com/), \nmerge support would be a nice-to-have feature. Mike may get 50 or\n100 comments per day. In his case it does not matter, he's curating\ncomments anyway, but manual merging would quickly become a chore,\nespecially when threads should be preserved.\n\n### Implementation\n\nIn the end I have decided to generate a separate folder \"comments\"\nin the Hugo project's top-level directory. Its directory structure\nmirrors that of \"content\". For every post with comments, there is\na directory containing one JSON file per comment. The comments\nare not merged into the markdown files, but instead they are\nread by Hugo and rendered by the theme's \"single.html\" template.\n\nThe approach is based on the excellent article \n[How To Have Comments In Hugo Without An External Service](http://saimiri.io/2016/06/comments-in-hugo/) by \n[Juha Auvinen](https://github.com/saimiri). His tool \n[hugojoco](https://github.com/saimiri/hugojoco) is meant to listen\non a server, therefore I didn't use it directly, but the base\nideas are his.\n\nFor any further details please look directly at the \n[repo of my blog](https://github.com/amanessinger/manessinger.com).\n\n## How to use this tool\n\nCongratulations, you've made it down here and you seem to be\nstill interested. Let's talk about using this tool.\n\n1. Install [Go](https://golang.org/) and learn it. At least \n   play through the [Tour](https://tour.golang.org/). You\n   should also try to understand \n   [Go Templates](https://golang.org/pkg/text/template/) as I use \n   them in the converter, and so does Hugo for templating. You\n   need that knowledge, or otherwise you'll have a hard time\n   adapting Hugo themes to your needs.\n\n2. Fork this project. You will need to make changes for your \n   exact situation. No need to create pull requests, I won't\n   incorporate those changes. If you find bugs though, a pull\n   request would be perfectly appropriate.\n\n3. [pkg/converter/config.go](pkg/converter/config.go) is the \n   one file that you **must** adapt.\n\n4. Test your changes, change to the directory \n   [wordpress-xml-to-hugo](wordpress-xml-to-hugo) and \n   `go install` the program.\n\n5. Have an XML export of your WordPress blog ready.\n\n6. Install [Hugo](https://gohugo.io) and use it to create a new\n   project for your blog. Use `git init` to add it to version\n   control\n   \n7. Install a [Hugo theme](https://themes.gohugo.io/). I've used \n   [Natrium](https://themes.gohugo.io/hugo-natrium-theme/), \n   checked it out in my blog project's \"themes\" folder \n   (not as a Git sub-project), removed the `.git` subdirectory of\n   the theme and instead added the theme to my blog project's repo.\n   This way I lose any future theme improvements, but I need to\n   make big changes anyway, for instance for rendering comments.\n   \n8. Make sure that your theme expects posts under the path defined\n   as `PostDirectoryContentSubPath` in \n   [pkg/converter/config.go](pkg/converter/config.go). Otherwise \n   change that definition.\n\n9. Go into that project's top-level directory and from there run \n   \n       wordpress-xml-to-hugo \u003cpath-to-wp-export\u003e .\n\n10. Use `hugo server` to inspect the result. You might need to\n   look at a lot of posts until you can be sure that the \n   conversion is acceptable. Some iterations might be needed.\n\n11. When you're sure that the result is as good as an automatic\n   conversion can reasonably be, commit the converted result to \n   version control. All further changes will be manual.\n\n12. Decide about a way to display existing comments and to handle\n   future ones. Look at\n   [my blog](https://github.com/amanessinger/manessinger.com) for\n   an example. As of 2018-08-15, display of comments is done,\n   enabling new comments is work in progress.\n   \n## Conclusion\n\nAs far as I'm concerned, this project is done. I have achieved my \ngoals, the blog has successfully been converted, all information\nthat I care about has been ported over.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famanessinger%2Fwordpress-xml-to-hugo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Famanessinger%2Fwordpress-xml-to-hugo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famanessinger%2Fwordpress-xml-to-hugo/lists"}