Enjoying the content? If you value the time, effort, and resources invested in creating it, please consider supporting me on Ko-fi.
 
  
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/yyyytoyyyy-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]::WriteAllLinesas I foundWrite-Outputwasn’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.
