Migrating my blog from Wordpress.com to a self hosted blog using Ghost, was a lot of work. More then I have anticipated. If I look at my Trello board where I keep track of it, I see that I've started to work on this on January the 20th and I am not yet done. It is up, it looks good, but there are still some items on my TODO list. It takes me that long for three main reasons:
- it is a lot of work
- I work on it only on weekends or at night (I'm going through a very busy period with the real work), and
- I'm a very pretentious with the result (I can spend hours on a small detail until it looks / works as I want).
But it's worthy! I'm very happy with the result. I like how it looks and how it works and I've achieved everything that I wanted when I've started this. However, there are a few things that I miss from Wordpress (I'll get to them below).
In all of this work I was guided by the experience of others who went through a similar process. I've learned by reading many blogs, like Troy's Hunt, who not only that contributed in making my choice of going with Ghost, but he was also migrating his own blog at the same time with me :)
@florincoros nice one!
— Troy Hunt (@troyhunt) April 19, 2016
So, I've decided to take some time and detail how I did it, maybe it'll be useful to other who consider such a migration. Troy did the same here, and even if I've learned a lot from him and there are similarities, we had different contexts so there are also differences.
It is going to be a long post, but I have made below a list with all the sections, so you can easily jump up and down to what might be useful for you.
Sections
- Choosing the Platform
- Migrate the Content
- The Theme
- Hosting
- Code Syntax Highlighting
- Redirect from Wordpress
- HTTPS
- What I Miss from Wordpress
- TODOs
Choosing the Platform
The goal wasn't Ghost. As I said in the post that announced the new look I wanted to get away from a free Wordpress.com site, with a free theme and to give the blog a more professional look. The options I had in mind were:
- build or buy a theme for Wordpress and migrate to a professional Wordpress host provider
- Ghost self host or Ghost Pro
- Github Pages
I was biased against Wordpress, because I had a lot of pain with it. It always felt like it is too complex and rigid for what I wanted. I was still considering it thinking that in a payed service it should be better. My biggest pain with it was with writing the posts. Some of my posts have a lot of code, and pasting the code from Visual Studio and then formatting it too look good in the post was a cumbersome process. I was waisting a lot of time with that.
Ghost was attractive because of the Markdown. I can paste code from Visual Studio in the editor and it keeps the formatting. No more inserting spaces and break lines manually. Plus I can easily mark a code structure using the code inline construct like this
.
I've started by reading how other technical bloggers feel about it. I've read the following posts (at least):
- Michele Bustamante: i. love. ghost.
- Troy Hunt: Creating a blog for your non-techie significant other; the path to Ghost
- Scott Hanselman: How to install the nodejs Ghost blogging software on Azure Websites
- Ryan Hayes: Ghost VS WordPress (and Why I Migrated Back to WordPress)
After reading these I was pretty much convinced. I know I should have given a better chance to Wordpress or Github Pages, make a more informed decision, but... this felt right. Wordpress failed me once and Github Pages feels more focused on building sites for projects, rather then blogs.
Next to make a final call on this I needed to get some info about:
- what it means to migrate the content from Wordpress to Ghost
- how hard is to build / buy / make a theme for Ghost
Migrate the Content
The posts content is the most valuable thing you want to migrate. This is in fact the point of the migration, otherwise we would talk about a new blog. For me it was also the most time consuming thing.
Before making the final decision of going with Ghost, I've started to search and read about the migration to it. Among others, I've read these:
- Ghost for Beginners: Migrating from WordPress to Ghost
- All About Ghost: Moving your Blog from WordPress To Ghost
Seams not only doable, but quite easy. Lets see how it went.
I've had 31 posts and a few pages to migrate. Not too many.
I could not just move over the HTML of the old posts in Ghost markdown, because it was an ugly HTML. It had inline CSS, because in Wordpress I was using a free theme that was not offering much, so I was colouring some text headers in the editor and that resulted in HTML mixed with CSS, like this:
<span style="color:#50b4c8;">Part 1: reviewing few concepts</span>
This colour wouldn't look right with the new blog design.
Another blocker was the code snippets. The code would have been unreadable elsewhere than in that particular Wordpress theme.
So I've used the Wordpress Ghost Plugin to migrate the content as explained in above articles. But it wasn't straight forward. First you cannot install plugins on a free Wordpress.com site. Secondly, the images are not migrated by the plugin.
I've followed the instructions from this post by Hugh Rundle, and I have used a self-hosted Wordpress install as an intermediate step, as follows:
- I've installed a fresh Wordpress on Azure. I found in Azure Marketplace a Wordpress + Mysql machine, published by Docker, which I deployed easily as a standalone VM (any other Wordpress installation will do).
- I've exported the content from Wordpress.com using this guide and then I've imported it on the installation from Azure.
At this point I had my content on the self-hosted Wordpress on Azure. On this one I could install plugins.
The images are still on the Wordpress.com site, so when I open a post from the self-hosted site, the images are downloaded from florincoros.wordpress.com
. If now I'd migrate the posts to Ghost the images remain on florincoros.wordpress.com
. I didn't like this. I can't download them from Wordpress.com to upload them on Ghost, because Wordpress does not offer such an export and even if it would, all the URLs would need to be changed and I'd be limited to a self-hosted Ghost.
What I did was to use Cloudinay. Cloudinary is an image management solution in the cloud. You can upload your images there and then you can easily use them anywhere with some neat image manipulation features based on the URL format. They also have an useful Wordpress plugin.
Having the blog copied on the self-hosted Wordpress on Azure, I did the following steps to migrate to Ghost:
- Install the Cloudinary plugin for Wordpress and upload all my images on Cloudinary. The plugin also modified all the URLs from my posts, and now all posts take the the images from Cloudinary and not from
florincoros.wordpress.com
.
- Install the Wordpress Ghost Plugin and use it to export the content in Ghost format
- Import the content on my new Ghost installation
After this I've had the bulk of my content available on the new blog. There were still a few things to adjust:
- The code snippets
- Wordpress uses
[code lang=”csharp”] ... [/code]
to mark a code block. This is what I got in the post text :( . It doesn't look like code. - some snippets had poor formatting due to the spaces I've manually added with Wordpress editor
- Feature images
- Because I had a poor free theme on Wordpress, for each post I have inserted its image as content in the post and also selected the same image as the featured image of the post. Now in Ghost, each post gets the image twice :(
- Images URL
- Cloudinary plugin did a good job for most of the cases, but there are few images who point to an IP address. I guess somewhere along the import/export plugins there was a bug that caused this (that IP address is the one that Azure has given to my Wordpress VM).
- all the URLs were rewritten with HTTP not with HTTPS. I am not sure why. Maybe I've missed a setting in plugin, maybe because they may have been HTTP on the original Wordpress too.
- Words in italics
- in the Wordpress editor it often happened that when I wanted one word from a phrase to be in italics to also make the following space char in italics. When this is converted to markdown we get a space between the word and the
*
. The result looks like this: *word in italics **next word, instead of word in italics next word.
All of these I had to fix manually by editing each post. Here is the time consuming part. I get a lot of speed by using an smart editor like Visual Studio Code, where I can find & replace all [code lang=”csharp”]
with ````language-csharp` or I can use the multi-cursor or other neat features for fast editing to format the code. However, its still a lot of manual work. Maybe if I had hundreds of posts I would have built a tool that parses the json the Wordpress Ghost Plugin generates, and fix all of the above there before import it in Ghost.
The Theme
I knew from the beginning that I can't build or customise a theme. I don't have web design skills and I'm not good with HTML or CSS. So, even before deciding to go with Ghost, I've asked my good friends at Dali Media if they have a preference of building a my blog theme for Wordpress or Ghost. Even if they have a lot of experience with building Wordpress sites, I got the feeling that it wasn't all that pleasant. They preferred Ghost event though they didn't know it.
For me the theme was a payed service. I've sit down with them, explained what I'd want and in a few days, after a few iterations, I've got it. I'm very happy with the result, it looks good on the phone, on the tablet or on the desktop, and everything I wanted was doable quite fast. I don't know all the details, but I can say that if you are good with HTML, CSS and JavaScript you can figure out quite fast how Ghost works and how to build or customise a theme.
I know that they had to do some custom work to have the dropdown in the menu I wanted for the Training entry, because Ghost does not support this yet. The drawback for me is that I cannot maintain it from the /ghost/settings/navigation/
and I'll need to change the theme files when I want something changed in there. Another thing hardcoded in the theme is the bar I have on the right. This also needs to be maintained from the theme's code and not from the editor. The Recent posts box uses the API Ghost offers, so that is not hardcoded, but if I'd want to take out this box, I'd need to change the template for the right bar. I don't mind these. I will change them rarely and the code is clean. I've put it in git, so I have history and from there I can easily deploy updates to these parts.
The entire work for the theme was only in its folder, so with what I've got from the guys at Dali Media I can go either to a self-host, either to Ghost Pro.
Having the content migration figured out, and a theme for Ghost built the next thing was to decide on hosting.
Hosting
From the very beginning, I wanted to go with a SaaS offer. After I've decided to go with Ghost, I was convinced to go with Ghost Pro offer. So the entire work on the migration and on the theme went on this assumption. I was using an installation on Azure, but only for development and testing purposes.
When I've reached the Migrate to GhostPro item in my TODO list, this changed. The price was 29$ / month. I was remembering it to be a lot cheaper when I've first read about it on Troy's blog. I've searched a bit and indeed the price grew from 5$ / month to 8$ / month, then to 10$ / month and now to 29$ / month for the smallest plan. Troy makes in his post a very compelling argument in GhostPro favour, but 228$/year seems a bit too much for hosting to me. Adding to this that their price grew more then tree times in about a year... I decided to wait a bit before I go on Ghost Pro. So, I've turned my Azure installation in the production one, at least for now. The first time I'll feel that the extra work that I need to do to manage the self-hosted installation on Azure is significant, I'll move to GhostPro.
On Azure, I have easily installed it as an App Service on the free plan, using this Github repository from Felix Rieseberg. I have also made an upgrade of Ghost to see how complicated that is, before making the final call with the self-hosted installation. I have used this upgrade tool from the same Felix Rieseberg. Again it worked with a simple button hit. So, it seems that self host is not going to be that difficult. I get the PaaS from the Azure, so I don't need to think about the OS, web server etc. and I just need to upgrade Ghost.
For developing and testing the free plan was more then enough. When I've decided to use it for production I've upgraded to the basic plan, because I wanted a better service. I've used features like AlwaysOn, Custom Domain, etc. This makes sense, money wise, only because I have enough credits in my Azure subscription. A B2 costs 55.21EUR a month, which is a lot more than GhostPro. Maybe the shared plan (D1), which is 8.16EUR a month would also be enough.
To setup my domain I have followed this guide. After everything was done, I also had to setup the domain name in the Azure portal in the Application settings, so Ghost knows it.
With this done, I had my site migrated and working at http://oncodedesign.com
. At this point it wasn't HTTPS, yet. I was using https://oncodedesign.azurewebsites.net/ghost
to login and access the admin page, but the rest was HTTP. I wanted to get to a version that I could show to the world, before fixing this.
Code Syntax Highlighting
For code syntax highlighting I have used PrismJS. I appreciate the most two key aspects of it:
- very easy to install - I've just followed the steps here and I was done in a few minutes
- highly extensible - The CSS is very clean. Even if I'm not good at CSS I could easily change the colours of some C# tokens that did not fit well in my theme design
Having the code syntax highlighting done, the theme installed and configured, next I have spent some time to adjust the menu, the content of the pages it links and to fix the content of most of the posts after the migration. With this done, the first version of my blog on Ghost at oncodedesign.com was ready to be shown to the world. To make this happen, the next step was to redirect the requests from florincoros.wordpress.com
to the new address.
Redirect from Wordpress
The hole idea here is that when someone uses a link to a page from my old blog, it works. She should be redirected to that page on the new blog. This is useful for all the cases when links are saved in bookmarks, or referenced in other sites or on twitter, facebook, etc., and most important for SEO. I want all the Google / Bing ranking that my posts and pages made on the old blog to be used by the new one as well. So all the redirects need to be 301.
The first step here is to make the florincoros.wordpress.com/<some_url>
to redirect to oncodedesing.com/<some_url>
. This is easy to achieve with some money :) Wordpress.com offers this as a service for 13$/year. To configure it I just had to follow the instructions here.
The next step was to rewrite the Wordpress URL into Ghost URL. By default an Wordpress URL is like: florincoros.wordpress.com/2015/05/06/the-post-title
. Now this gets redirected to oncodedesign.com/2015/05/06/the-post-title
and the request fails because in Ghost this post is at oncodedesign.com/the-post-title
. So, to fix it I have followed the instructions from this blog post, only that I've used the IIS URL rewrite module and specified the rewrite rules in the web.config
of the Azure Web App.
Another thing that I did, here was to redirect the www. subdomain to naked domain, meaning that a www.oncodedesign.com is redirected to oncodedesing.com. Here Ryan Hayes gives a good explanation on why this is important and how to do it.
In the end my redirect rules look like this, in the web.config
:
<system.webServer>
...
<rewrite>
<rules>
<rule name="Redirect to non-www" stopProcessing="true">
<match url="(.*)"></match>
<conditions>
<add input="{HTTP_HOST}" pattern="^oncodedesign\.com$" negate="true"></add>
</conditions>
<action type="Redirect" url="https://oncodedesign.com/{R:1}"></action>
</rule>
<rule name="Redirect Wordpress posts" stopProcessing="true">
<match url="\d{4}\/\d{2}\/\d{2}\/(.*)$"></match>
<action type="Redirect" url="{R:1}"></action>
</rule>
<rule name="Redirect Wordpress training pages" stopProcessing="true">
<match url="training\/(.*)-training"></match>
<action type="Redirect" url="training-{R:1}"></action>
</rule>
<rule name="StaticContent">
<action type="Rewrite" url="public{REQUEST_URI}"/>
</rule>
<rule name="DynamicContent">
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True"/>
</conditions>
<action type="Rewrite" url="index.js"/>
</rule>
</rules>
</rewrite>
</system.webServer>
The rules that I've added are: Redirect to non-www
, Redirect Wordpress posts
and Redirect Wordpress training pages
. It is needed that these rules are before the existent ones (StaticContent
and DynamicContent
), which are needed by Node / Ghost. It gave me some headaches to figure this out...
Visual Studio Online "Monaco" was of great help to edit the web.config
and test the redirects directly on Azure.
If I were on Ghost Pro, I couldn't have written the redirects myself. They also don't yet have an admin console where you could configure them. However, from what I've read, if you email the support with the redirects you want they configure them for you.
With this done, my blog was migrated. From this point on I could publish new posts and add new pages. It was the moment I have announced the new look.
HTTPS
The last thing I did was to set everything to go through HTTPS. For this I rely entirely on CloudFare. Troy Hunt describes it very nicely here and I have just followed his steps. Indeed it only took a few minutes to setup and leverage all the benefits. Besides the HTTPS, and the potential performance gain, I like the most the email obfuscation feature.
What I Miss from Wordpress
Even if I am very happy with Ghost and I think it was a good call to leave Wordpress. However, there are a few features that I am missing:
- review functionality
I value a lot the reviews I get for my writings, any of my writings: code, articles, documents, e-mails etc. All the posts or pages that I published on this blog are reviewed at least by one person. I am in luck that my girlfriend is also a developer, she reviews most of my posts.
Wordpress has a review functionality which I used for this. It is not much, but you could ask for a review by e-mail, which was convenient. Now, I either make an account on my blog for all people that I want to ask for a review, or... send the article by email.
- social networks integration
I want to tweet and post on Facebook, LinkedIn, Google+ and the others when I publish a new post.
Wordpress has many plugins for social networks, which give this out-of-the-box. Now, I am posting by hand, and I am planning to setup some IFFFT recipes to automate it.
I also like to have my tweets on the blog. I had this easily setup on Wordpress. I don't have yet any idea on how to put this on the new blog.
- comments
I love to get feedback or questions on my posts. Ghost does not have this out of the box. I plan to integrate Disqus for this. Until then please use my contact info when you want to send me feedback, questions, ideas or thoughts.
TODOs
At this point my new blog is ready. I can easily publish new posts and everything should work well. However, there are still a few items on my Checklist in my Migrate the blog at oncodedesign.com Trello card:
None of these are critical. I think most are nice to have. I will try to do them when a find a few hours here and there. However, I would like to spend the little time I have for my blog to write new technical articles, so it may take a while until I will move this card to the Done column.
This was pretty much the work that I did to redo my blog. I hope you like the result. I like it for sure, and excepting the manual fix of the content, I also enjoyed working on it and learning new things on the way.
If you find yourself doing such a migration and you get stuck don't hesitate to ask me, I might have gone through the same.
We share our insights and experiences from working on client projects and teaching developers.
Our articles blend technical expertise with real-world challenges, offering a unique perspective on Code Design.
For us, Code Design means structuring code to ensure predictability and making it easy to manage and adapt long after it's written.