Moving a Medium blog to Jekyll, Netlify & Cloudflare Workers

Marc Roberts
Marc Roberts
Chief Technology Officer at hiyacar
Moving a Medium blog to Jekyll, Netlify & Cloudflare Workers

We recently moved our blog away from Medium, replacing it with several services working together. Here’s why and how.

Medium

This blog used to be hosted as a Medium publication under a custom domain (blog.hiyacar.co.uk). This was chosen for a number of reasons:

  • It involved no development time (other than DNS updates)
  • It was free
  • The writing tools are easy for any writer to use
  • Discoverability of our posts to the wider Medium readers

Ideally, for SEO purposes, we would have our blog posts served under the same domain name as our main platform domain. Medium is also limited as to how you can customise the layout and design. Over time the value of the discoverability has fallen and the importance of SEO has risen. This is something we have wanted to revisit for a while, now is that time.

Cloudflare Workers

Our platform runs behind Cloudflare which provides us with a layer of tools and services to increase performance and improve security. One of their more recent features is Workers, allowing (Javascript) code to run on their distributed systems for each request even before the request reaches our platform. Rather than building blog functionality in to our platform we thought it would be possible to use Workers to proxy requests for /blog/ URLs through to another host serving the blog.

This can be done very simply with this short worker script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
async function handleRequest(request) {
 
  const url = new URL(request.url)

  // Where to proxy the requests through to
  const origin = 'https://new-blog-host';

  // We want to send any redirects back to the client, 
  // not follow them here.
  const fetch_opts = {
    redirect: 'manual'
  }
 
  // Fetch the destination and respond
  return fetch(origin + url.pathname, fetch_opts)
}

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

Now we need to create and host the blog somewhere before we connect the /blog* URLs to this worker.

Jekyll

Having used Jekyll many times before it was an obvious choice to use to create this new blog.

We started with a basic Jekyll theme that is similar to the Medium layout - Mediumish - and customised it a little to more suit our needs.

Medium allows individuals to export all their posts but it does not support this for publications. Each of our blog posts had to be manually recreated as markdown posts within our new Jekyll site. Thankfully we have under 100 posts and the Paste to Markdown tool helped speed this up significantly.

Jekyll takes these markdown files and HTML templates and creates a folder full of static files that need to be hosted somewhere. Next up we need somewhere to host these static files, and ideally build them for us too.

Netlify

Netlify is a platform for building and hosting static sites and comes with support for Jekyll ‘out of the box’. We connected it directly to our version control system and it automatically rebuilds and deploys the blog whenever there’s a change.

If this is something you’re looking for, I’d highly recommend it as it is so easy to setup.

Redirects

Firstly we need to redirect all blog.hiyacar.co.uk/xxxx requests to www.hiyacar.co.uk/blog/xxxx. We achieved this by adding a new Cloudflare page rule. We’re using an HTTP 301 Permanent redirect here to ensure that any incoming links pass through search engine value.

Cloudflare Page Rule for redirect

Next, we needed to remove the IDs from the URLS. Each Medium blog post URL ends with an 11 or 12 digit hexadecimal identifier, we don’t want or need these, so the Worker code was updated to find these, remove them, and redirect (again with an HTTP 301 Permanent redirect) to the updated URLs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
async function handleRequest(request) {
 
  const url = new URL(request.url)

  // The post ID suffix that Medium adds to post URLS,
  // we'll remove this shortly
  const medium_blog_regex = /-[0-9a-f]{11,12}$/;

  // Where to proxy the requests through to
  const origin = 'https://new-blog-host';

  // Does this have the ID suffix?
  if (url.pathname.match(medium_blog_regex)) {
    // IF so, remove it and issue a permanent redirect
    const new_url = url.pathname.replace(medium_blog_regex, '');
    return Response.redirect('https://www.hiyacar.co.uk' + new_url + '/', 301)
  }

  // We want to send any redirects back to the client, 
  // not follow them here.
  const fetch_opts = {
    redirect: 'manual'
  }
 
  // Fetch the destination and respond
  return fetch(origin + url.pathname, fetch_opts)
}

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

While we were configuring these redirects we also took some time to recategorise all our blog posts. While hosted on Medium we used various categories in order to gain more exposure to our articles. Now that we have more content and are hosting it ourselves we can focus on fewer categories.

These redirects are handled at Netlify in the configuration file, here are a few examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[[redirects]]
	from = "/blog/tagged/uk-day-trips"
	to = "/blog/category/experiences/"
	status = 301

[[redirects]]
	from = "/blog/tagged/day-trips"
	to = "/blog/category/experiences/"
	status = 301

[[redirects]]
	from = "/blog/tagged/life-lessons"
	to = "/blog/category/tips/"
	status = 301

[[redirects]]
	from = "/blog/tagged/life-tips"
	to = "/blog/category/tips/"
	status = 301

[[redirects]]
	from = "/blog/tagged/:tag"
	to = "/blog/category/:tag/"
	status = 301

# missing categories to homepage
[[redirects]]
	from = "/blog/category/:cat"
	to = "/blog/categories/"
	status = 302

Once all this was in place, we attached the worker to a new route to start it handling all /blog* URLs

Cloudflare Worker Route

All that was left then was to write about the whole process 😀

Have something to add? Discuss in our community forum.