Migrating my Personal Site to Contentful, Netlify, and Google Analytics

Published by   on Sun Jul 21 2024

contentful2024learning in publicblog

An image of a migration

This weekend, I started writing an exciting blog post about a project that a couple of friends and I recently became feature-complete on. However, I noticed that the way I was writing blogs was simply not maintainable anymore.

I went to the drawing board to revisit my website to investigate the pain points. I came up with the following points:

With these pain points in mind, I decided to start with reducing costs.

Migrating to Netlify

I migrated my site off Amplify and onto Netlify. Netlify hosts my site for free! My domain is still registered through Route53, so I still have a tie to AWS (though I may choose to transfer the domain later). It was a simple process, just set up Netlify to track my GitHub repo and update the domain records to point to Netlify's load balancer, and voilà! The site is up. It's worth noting that this did result in about 24 hours of unsecured traffic due to an invalid certificate, but it's not a big deal.

Onboarding to Google Analytics

I wanted to get page-level data for when folks visit my blogs. That way, I can know which topics are more interesting than others. Google Analytics onboarding was also quite easy. All I had to do was add the Google Analytics library into the codebase and configure it to collect analytics on each page. There's some additional configuration I'd need to do for more in-depth metrics, but I've got a page view counter working and that was my goal!

Onboarding to Contentful

Contentful is a content management system which I chose primarily because it has integrations with Netlify. I use Astro, a barebones frontend framework for building static websites. One thing to consider about static websites, though, is that they need to know all of the routes for the site at build time. This slightly complicated the site, but it allowed me to shrink the client significantly and offload the content to Contentful.

Contentful allows you to "model" your content however you want and has a pretty wide variety of supported content types. You do this by creating "Content Models." I can reduce this to just defining a schema for your content, so the API contracts are consistent.

To start, I defined a couple of different content models: a "BlogPost" model and a "Profile" model. The BlogPost was the structure for blog post entries. It included the following attributes:

The body of the content model is a "rich text" type, which structures the data in such a way that you can use Contentful's SDK to parse it into HTML. It's pretty nice and handles about 80% of my use cases; however, I'll touch more on that 20% later.

The Profile content model was mainly to set the "author" field. Contentful supports injecting references to other content models from a root model, so I created the Profile model separately in case I wanted to get creative later. The profile model included the following attributes:

Full transparency, I have not ended up using the profile image. I didn't get it to work right away and moved on. Nested content references are a little non-trivial to work with. In rich text fields, it's easy because the HTML parser will automatically pull the nested content references and render them. Well, not automatically... I guess. There's some custom stuff you have to do, like specify how to render specific content types. For example, I specified to render any nested image blocks with a specific HTML element configuration:

const options: any = {
  renderNode: {
    [BLOCKS.EMBEDDED_ASSET]: ({ data: { target: { fields }}}) =>
      `<img src="${fields.file.url}" width="100%" alt="${fields.description}"/>`
  }
};

documentToHtmlString(content, options);

The Simple Part

Once all of this was set up and I was able to hook into Contentful's SDK to pull content, I simply had to pull my articles (written in MDX) and migrate them one by one over to Contentful. It wasn't too bad, aside from one major pain point: Contentful's rich text editor doesn't parse markdown. I had to go through and manually edit every header, code snippet, bold, and italic text and use the UI buttons or keyboard shortcuts to format the text, rather than just use *stars*, _underscores_, or `backticks`. But I did, and it worked out fine in the end... aside from one remarkably dumb case.

The Not-So-Simple Part

Code blocks. In the rich text editor in Contentful, there is only an inline code formatter. That means if I try to format multiple lines of code using the built-in code formatter, it renders like several lines of in-line code. Here's an example of how hideous that is:

def main():

thisSucks = true

if thisSucks == true:

print('wow, this really sucks!')

>> wow, this really sucks!

That is hideous.

I scoured the internet for a couple of hours and found that the best way to solve this problem was to create another content model, specifically for code blocks. This allows me to add an option for formatting those specific references in my options configuration. I'll show the code using the CodeBlock content type solution:

    const options: any = {
        renderNode: {
          ...
          [BLOCKS.EMBEDDED_ENTRY]: (node) => {
              const { sys, fields } = node.data.target;
              if (sys?.contentType.sys?.id === 'codeBlock') {
                  const { value: code } = highlightjs.highlight(fields.code, {
                      language: fields.lang,
                  })
                  return `<pre>${code}</pre>`;
              }
              return undefined;
            },
        },
    };

Thank you, Jacob Stein.

The great thing about this is that it also uses highlight.js to format the code based on the provided language. Check out the link to Jacob's blog to learn more about how this works. I referenced his solution nearly 1:1.

Cleanup

I wanted to shrink my client as I was onboarding, so my migration process was to migrate one blog to the CMS and then delete it from the client. One of the reasons I had to do this was because I wanted to ensure backward compatibility for the blog entry links, and Astro favored hard-coded MDX paths to slugs when it came to sourcing content for a specific link. To see if a blog from Contentful looked correct on my site, I had to either use a test slug or delete the existing MDX file. So as I went, I deleted the MDX files once the Contentful blog looked correct.

I also later added a tags field to the BlogPost content model so I could add the tags from each blog that I previously stored with MDX. I migrated the tags experience to depend on Contentful and deleted any leftover code I no longer needed.

I also deleted all image assets from the client that were related to the blogs, as those no longer needed to be stored on the client anymore.

End Result

My site is now pulling all blog and tagging content from Contentful and all assets have been offloaded so they're no longer hard-coded into the client. I can track see page view metrics for all routes on my site, and the cost to operate my site is just the cost of the domain renewal.

One downside is that writing blogs takes a bit longer when formatting text, with code block formatting taking the most amount of additional time. However, adding images is a lot easier and I don't have to copy all the MDX boilerplate around either. Additionally, the MDX editor in VSCode would frequently tab out suggested code snippets rather than allowing e to write uninterrupted. Contentful's rich text editor is in-browser and works with Grammarly. I've caught and fixed a ton of issues with my past blogs thanks to this.

Overall, think this was a solid upgrade to my site. I recommend trying it out!

A screenshot of the Contentful rich text editor