Migration from Wordpress to Static Site Using Hugo and Netlify - A Step-by-Step Tutorial

After a gap of almost three years, I recently decided to relaunch my six year old self-hosted wordpress blog TechnoDuet. It was high time to take advantage of the modern static site generator tools along with the availability of some really awesome free static-site hosting services.

Improved performance, ease of maintenance, scalibility, no security concerns due to database and plugins, simplicity of writing blog posts in markdown, version control for content, option of free reliable hosting with custom domain, and the convenience of continuous automatic deployment are some of the advantages that compelled me to migrate and convert my wordpress blog to a static site.

I used Hugo as my static site generator with the site being deployed using custom domain on Netlify. To take advantage of continous deployment by Netlify, I have used bitbucket to host my private git repository. Changing the website/addition of new content is now a breeze - Whenever I commit and push changes to my remote git repo, Netlify automatically triggers the build and deploys the updated website within seconds. Pretty cool!


1. Hugo Installation

Step 1: For MacOS install Hugo via Homebrew. Open the Terminal:

brew install hugo

To fix any permission errors:

sudo chown -R $(whoami) $(brew --prefix)/*

Step 2: Create a folder Hugo. Inside that folder, run command:

hugo new site mySiteName

where mySiteName is the name of your website. This creates a new Hugo Site in the folder mySiteName
Hugo explains the directory structure in detail.

Step 3: Navigate to the new directory:

cd mySiteName

Step 4: Create a new local git repository:

git init

Step 5: Select a theme from themes.gohugo.io

Step 6: Go to /mySiteName/themes folder: cd themes and add the selected theme (I selected the theme MainRoad):

git submodule add https://github.com/Vimux/mainroad

Step 7: Copy the sample file config.toml from your downloaded themes folder to /mySiteName/. Open the file in a text editor and customize the various parameters such as website title, navigation menu, social media links etc. Make sure that the value of theme parameter is identical to the folder name of your downloaded theme.

Step 8: Start the server:

hugo server

and view the site with your installed theme at http://localhost:1313/. Hugo server supports LiveReload - any change causes the site to be automatically rebuilt and reloaded in the web browser. The server can be stopped by pressing ctrl+C from command prompt. There are no blog posts added yet.

Step 9 (Optional): To customize the theme, copy the relevant theme file to the /mySiteName/layout folder and make the edits to files in this folder. For example to edit header.html inside the downloaded theme (Mainroad) folder:

Copy mySiteName/themes/mainroad/layouts/partials/header.html to


Step 10 (Optional): To make changes in the css file copy the style.css file from the themes folder to the folder: /mySiteName/static/css/ and make edits here.

2. Content Export from Wordpress to Hugo

The next page migration step is exporting the wordpress blog posts and images and importing these to the Hugo website. After trying various plugins and tools, the following finally worked for me:

Step 1: Export blog posts as XML file - From Wordpress Admin dashboard, select tools->export. Select all content and download the XML file

Step 2: Conversion to Markdown format - Download and install ExitWP for Hugo. Save your exported XML file in the wordpress-xml folder inside the uncompressed ExitWp folder and run the command:


The blog posts will be converted into Markdown format and saved in the build folder inside the uncompressed ExitWp folder.

I had issues running ExitWPForHugo on my Mac, but it worked fine on a windows machine

Step 3: Copy the converted .markdown files in the /mySiteName/content/post folder.

Step 4: I had to manually edit the converted files as well. Although the ExitWP did a good job, but there were still some places where the formatting was not correct. Moreover, the wordpress Caption shortcode also did not automatically convert into Hugo figure shortcode and to be manually changed.

Step 5: Since my old website was on shared hosting with cPanel, I easily compressed and downloaded the media folder inside wordpress installation directory: /wp-contents/uploads. To prevent the broken URLs of images, I simply copied the entire contents of this folder to /mySiteName/static/wp-contents/

Step 6: Fix the Permalinks: The URls of my Wordpress posts were of the form: technoduet.com/post-title/ To preserve this structure, I added the following in config.toml:

  post = "/:title/"

3. Creating the Remote Repository

First, stage and commit files in your local git repository:

git add .

git commit -m “text “

Create a private repository on bitbucket and push your local git repository:

git remote add origin <Your_Bitbucket_Repository_URL>

git push -u origin master

4. Hosting on Netlify

Step 1: Create an account on Netlify. Follow the onscreen visual guide for quick and simple deployment of your website that will involve connecting and authorizing your bitbucket repository with Netlify.

I set HUGO_VERSION="0.49" in the setup panel as this was the version of Hugo I had used on my local machine.

Step 2: After deployment, I changed the Site Name in the Settings panel to technoduet from some previously auto assigned random name, so that it is accessible via technoduet.netlify.com

Step 3: The custom domain can also be easily setup via the GUI.

Step 4: (For Custom Domain Names) For Redirects: Create a file netlify.toml and place it in the root folder of website. Relevant part of my Netlify.toml:

from = "https://technoduet.netlify.com/*" 
to = "https://www.technoduet.com/:splat" 
status = 301							
force = true

from = "http://technoduet.netlify.com/*"
to = "http://www.technoduet.com/:splat"
status = 301
force = true

The website is now ready. For a new post or any site changes, you only need to commit and push changes to remote git and Netlify auto deploys within seconds!