In recent posts, I’ve talked about migrating from Grav to Hugo, at a high level.
Before I get down into the detail of setting up the new solution - I need to spend a little time on the changes you will need to make along the way for your content to be compatible with Hugo.
If you are migrating to Hugo from a different platform than Grav, the changes required are likely to be different, but may be similar to those that I experienced.
How do I start with Hugo?
I can’t recommend the Quick Start guide on the Hugo website enough - straightforward enough that even I could follow it ;-)
So, what’s different between Grav Markdown and Hugo Markdown?
Some of this will be theme dependent - I’m using the Casper theme.
- The directory structure is different by default, with only 4 top-level folders required:
- archetypes : comes with the Hugo download - these are the default archetypes or templates for content in Hugo
- content : surprisingly enough, your content goes in here - more detail on this below
- static : static content such as images go here - I found that unlike Grav, I couldn’t have images in the content folder - they had to be here
- themes : this contains any themes you’ve downloaded including their archetypes, config etc
-
There is also a config file at the top level that can be in either TOML, YAML or JSON formats (more info) - Casper theme requires the use of the TOML format
-
In the content directory, you can have any top-level pages other than the default index page (in the example here I have my Cookie/Privacy notice page and a mailing list signup page) and then a subdirectory for all other posts (in the case of the Casper theme - that directory has to be called post)
-
In that content\post directory, all you can have in there is your markdown files for your blog posts
-
In the case of the Casper theme, you can enable a number of features without installing plugins etc by editing the config file to enable them - below is mine with my particular settings mostly changes to examples or comments to explain them (
#
at the start of the line will comment out any setting that you don’t want processed - the defaults will then be used)
LanguageCode = "en-gb"
baseURL = "https://mydomain.net"
Title = "Website Title - displayed on the cover/header of the site"
canonifyurls = true
paginate = 5
DisqusShortname = "put in your Disqus shortname to enable Disqus comments on your posts"
theme = "choose the theme to use for your site"
[menu]
[[menu.main]]
identifier = "Home"
name = "Home"
url = "/"
weight = -100
## The menu Item's below I added so I could have ##
## more than just Home on the navigation menu. ##
[[menu.main]]
identifier = "Mailing List"
name = "Mailing List"
url = "/mailing-list/"
weight = -110
[[menu.main]]
identifier = "Cookies"
name = "Cookies"
url = "/cookie-notice/"
weight = -120
[params]
description = "Tagline for your website."
cover = "images/my_cover_image.jpg"
author = "Author name for posts on the site"
authorlocation = "Scotland, United Kingdom"
authorwebsite = "https://mydomain.net"
authorbio= "author bio information here"
logo = "images/my_website_logo.gif"
googleAnalyticsUserID = "UA-123456789-1"
# Optional RSS-Link, if not provided it defaults to the standard index.xml
RSSLink = "http://feeds.feedburner.com/..."
githubName = "vjeantet"
twitterName = "to enable link to your twitter feed, add your handle here"
facebookName = "to enable link to your facebook page, add your facebook page name here (not the full url)"
codepenName = ""
linkedinName = ""
stackoverflowId = ""
keybaseName = ""
flickrName = ""
instagramName = ""
email = "admin@mydomain.net"
pinterestName = ""
googlePlusName = ""
set true if you are not proud of using Hugo (true will hide the footer note "Proudly published with Hugo.....")
hideHUGOSupport = false
# Setting a value will load highlight.js and enable syntax highlighting using the style selected.
# See https://github.com/isagalaev/highlight.js/tree/master/src/styles for available styles
# A preview of above styles can be viewed at https://highlightjs.org/static/demo/
hjsStyle = "obsidian"
[sitemap]
changefreq = "daily"
filename = "sitemap.xml"
priority = 0.5
- I don’t plan to walk through everything in the config as that’s a post in itself - instead, a link to the relevant Hugo and Casper docs for this. The frontmatter/page headers format is different - specific information around Casper theme is at the doc link above.
How did you convert your site content?
So, as I mentioned in previous posts, I used powershell to help me convert my site rather than make all the require changes manually.
Specifically, here’s some snippets I came up with and what they achieved (anything in <angle brackets>
should be changed to your own values and of course I’m using my D:
drive here - change your path accordingly for your setup):
After copying all the Grav content (everything under user/pages/01.blog
in my case) to the content
directory for Hugo, I needed to essentially flatten the directory and place all the markdown (*.md
) files in the content
directory for my Hugo site (I recommend first of all trying this step with the -whatif
option so you can be sure what it’s going to do before you let it loose!):
dir D:\Hugo\sites\<mysite>\content\*.md -recurse|rename-item -NewName {$_.Directory.BaseName +".md"}
Next I placed them in the content\post
directory (again, -whatif
is your friend until you are sure it is going to do what you want):
dir D:\Hugo\sites\<mysite>\content\*.md -recurse|Move-Item -Destination D:\Hugo\Sites\<mysite>\content\post\
The next one is a bit of an eyesore (and I know a lot of powershell purists are going recoil in horror/object to my use of backticks as line continuation characters - feel free to smarten it up and share and I will update this post accordingly! This was thrown together and it shows):
dir D:\Hugo\sites\<mysite>\content\post\*.md | foreach {
$fname = $_.FullName;
$tags=Get-Content -raw $fname | Select-String '(?smi)\s{8}-\s(\w{1,})' -AllMatches | foreach {$_.Matches} | foreach { $_.Groups.Captures[1].Value };
$tagline="tags: [";$tags|foreach {$tagline=$tagline+'"'+$_+'",'};$tagline=$tagline.TrimEnd(",")+"]";
$content=(get-content $fname -Encoding UTF8 | `
Select-String -NotMatch '^hero_','^blog_','^show_','taxonomy','category','tag').Line `
-replace "(\d{2}\:\d{2}) (\d{2})\/(\d{2})\/(\d{4})\'","`$4-`$3-`$2T`$1:00+01:00'`r`n$tagline" -replace '/blog/','/post/';
$content=($content | Select-String -NotMatch '^\s{8}').Line;
[IO.File]::WriteAllLines($fname, $content);}
What the code above does is as follows.
For each file on the content\post
directory:
- Find all the blog tags
- Build a new tags line for this post in the format
tags: ["blog","tag1","tag2","tag3"]
as opposed to the Grav format which was in YAML - Get the file content as UTF8 (I had issues with content not being rendered properly otherwise) - stripping off tags not supported in Hugo such as hero tags, taxonomy, category etc.
- Amend the date tag value format from
hh:mm:ss dd/MM/yyyy
toyyyy-MM-ddThh:mm:ss+00:00
- Amend any link to an article with
/blog/
in the relative path to be/post/
- Strip out lines beginning with 8 spaces - this was a hangover from removing unsupported tags earlier - any tags removed which had indented additional settings, those are now orphaned and need removed.
- Write the file back out - I use
[IO.File]::WriteAllLines
as I foundWrite-Output
wasn’t correctly outputting as UTF-8 with BOM.
For the few, posts on this site that had images in the post folder, I moved them manually - but if you have a lot of those, you can move them to static\images
and amend your markdown to amend the links accordingly like I did with the blog/post change above.
Wrapping up
That covers the main challenges I had in converting my content - other than that, I found Hugo itself to be pretty straightforward to use. You can copy the code that Hugo generates for you to a webserver, an Azure Static Website, pretty much anywhere that can serve static web content.
In my next post I’ll drill down into how to setup a static website on Azure Blob Storage with Hugo content and utilising Azure DevOps to automatically build and deploy the site from a Github repo (and other tricks - such as compressing JPEG images during the build stage - smaller images take less time to deploy and crucially, load faster!).
As ever, thanks for reading and feel free to leave comments below.
If you like what I do and appreciate the time and effort and expense that goes into my content you can always