This weekend, I started the initial migration of Cushion’s marketing website to the Contentful CMS. This is the next big rock on my list that definitely needs to be done in smaller portions rather than all at once. The marketing site was originally built on CraftCMS, but after a few years, I ended up rage quitting and compiling the entire site to static on S3.

Since then, the only page I’ve actually updated has been the changelog, which lists any updates I make to the app. Since putting everything on S3, updating the site meant hand-coding the HTML files and re-uploading them to S3—knock it all you want, but it works. Realizing that this isn’t exactly the most sane way to update the website, especially when I have plans to update the rest of the site, I decided it was time to get back onto a proper CMS.

I’ve been using Contentful with my personal site for a while now, and I absolutely love it. The biggest hurdle is definitely the initial setup, since you need to BYOFE (bring your own front-end), but the authoring environment and flexibility is exactly what I want. I know for sure that migrating Cushion’s marketing site to Contentful will be an ordeal, but if I can chip away at it little by little, it shouldn’t be too bad—and that’s what I’m doing by starting with the changelog.

Migrating the changelog to Contentful consists of a handful of steps—many of which will feed two birds with one scone for future pages I need to migrate. First, I have to set up the new front-end, NuxtJS. Without getting into all the fancy modern web lingo, NuxtJS is a Vue-based framework for building websites or webapps. I use it for my personal site, but I also use it for Cushion’s invoices, so yeah—it’s versatile.

I’ve also decided to use Vercel (formerly Now, Zeit, etc.) again, like I do with my personal site, because it just works and gets out of my way. The one difference this time around is that I’m opting to generate Cushion’s marketing site as a static site. I realized recently with my personal site that the way I’m using NuxtJS and Vercel as serverless functions isn’t ideal, and occasionally throws 502s, so I’m going to do what the Vercel team suggests and simply generate a static site. At first, I was irked by this, thinking back to when Jekyll would take several minutes to generate my previous site, but even with ~400 changelog posts, NuxtJS takes ~20 seconds.

One caveat in switching to static, especially if you’re using Contentful, is that you need to make a few big API requests to Contentful beforehand to pull down all of your site’s content. Otherwise, NuxtJS will make individual API requests for each page, which will instantly exceed Contentful’s API rate limit of seven requests per second.

To accomplish this, I added a generate:before hook in my nuxt.config.js file, where I make the request and set the payload:

export default {
  hooks: {
    generate: {
      async before ({ setPayload }) {
        const { items } = await contentful.createClient({ ... }).getEntries({ ... });

        setPayload({ items });
      },
    },
  },
};

When you call nuxt generate, this hook will fire beforehand and populate your payload. Then, in your route’s asyncdata method, you’ll first check the payload, and otherwise fall back to the API request (which you want for local dev):

export default {
async asyncData ({ payload }) {
  if (context.payload) return { items: context.payload.items };

  const { items } = await contentful.createClient({ ... }).getEntries({ ... });

  return { items };
};

While this is great for fetching the data, I actually need data in Contentful to begin with, which means importing the ~400 changelog entries that live on a static page as HTML. These moments, where you need to write a scrappy one-purpose throwaway script, are one of my favorite parts of being a developer—it makes me feel like I’m a Junkyard Wars engineer who can turn a pile of car parts into a trebuchet. It also gives me the chance to challenge myself.

For this script in particular, I ended up writing it all in Chrome’s dev console. I grabbed all the posts with:

const posts = document.querySelectorAll('.changelog-post');

…then extracted the date from the title’s innerText and the list of updates from each <ul>. This was no problem because I could simply query the <li> tags to get an array and grab their innerHTML. This left me with a nice and clean JS payload that I could send to Contentful with little tweaking.

I used the contentful-management package to create the entries in Contentful, but first needed to transform the payload a bit to make it fit into Contentful’s fields. Because Contentful is good about localization, I had to nest each field’s value inside a en-US key, so instead of:

{ 
  title: item.title
}

…I needed:

{ 
  title: { 
    'en-US': item.title
  }
}

For the actual list of updates, which I have set up as rich text in Contentful (to allow for more than just lists), I needed to transform the lists from my payload into Contentful’s rich text JSON format. This would normally be an ordeal, but luckily the rich-text-from-markdown package exists, and does exactly what its name says—converts markdown into a rich text JSON blob. To make my lists markdown-friendly, I simply need to prefix each item with a hyphen. Easy.

This leaves me with the payload to insert and the requests that send the transformed payload into Contentful, but there’s one last hurdle—the API rate limit. Since I can only make seven requests per second, I would need to import seven changelog posts, then sleep for a second and repeat—and that’s exactly what I did, using a quick and easy sleep function:

function sleep () {
  return new Promise((resolve) => setTimeout(resolve, 1000))
}

I ran the script and in less than a minute, I had all ~400 changelog posts in Contentful. I refreshed the page and they were all there. Perfect.

Now, the changelog is one of many pages that I need to migrate to Contentful, and as I said, I’m not going to migrate them all at once, so I need to find a way to point https://cushionapp.com/changelog to my Vercel URL, but still point to S3 for the rest of the site. Fortunately, an AWS Cloudfront distribution rests in front of the S3 site, which lets me navigate scenarios like this one. All I have to do is create my Vercel URL as an “origin” and create a new “behavior” to point the /changelog path to the Vercel origin, but with caching disabled, since Vercel will handle that. After the distribution finished propagating, Cushion’s new changelog appeared, powered by Contentful, hosted by Vercel, and looking beautiful as ever!

changelog

From here, I can migrate other pages with the same approach, but I can also take another route. NuxtJS allows a “static” folder that simply moves its content to the “dist” folder to serve. Since the rest of the site is on S3, I could just put the rest of the site in the static folder and call it a day. And that’s actually what I’ll end up doing, but for now, I simply wanted to get a single page on the new system before flipping the switch on the rest of the site. Baby steps.

If you’re interested, you can check out the new changelog page here: https://cushionapp.com/changelog You might notice that it fashions a new design, which is true. You might also notice that each post is tagged with a repetitive “Changelog” badge, which is also true. There’s more to the story, but I’ll save it for the next post.