Part 1: Rebuilding a 1,000+ page WordPress site from scratch using Next.js and React

Part 1: Rebuilding a 1,000+ page WordPress site from scratch using Next.js and React
Cory Miller
Cory Miller is a Senior Front-End Developer on the Growth team ConvertKit. He spends most of his days hanging out with his wife and two daughters, playing and producing music, and geeking out about coffee.

When I first joined ConvertKit in January 2019, I inherited the keys to, a custom marketing site built on top of WordPress themes and plugins. The site was in a fairly fragile state, having passed hands between developers and designers, and was in need of some serious love.

At the time, updates were handled via new pages in WordPress or manually via FTP—no repository, no versioning, just download the folder and make it work with MAMP if you can.

Something had to change…but we didn’t seem to have the margin.

The Growth team only had a single developer (me) to handle current projects as well as battling technical debt. With every update it seemed like we made we were only slowly adding to the problem, and I knew at some point our fragile system would snap.

With several millions of unique visitors to our site every year, we needed to make a change.

So we decided to make a change.

In 2022 we rebuilt the entire marketing site using Next.js, using WordPress as a headless CMS, with only one developer and two designers. It was a 14-month project that existed alongside other marketing efforts the company needed.

In this blog series I want to take you through the journey of this rebuild:

  • The challenges of running a marketing site of our scale on top of WordPress, and why we ultimately chose to rebuild everything from scratch
  • Why we chose Next.js, React, GraphQL, and headless WordPress as our central stack, and some insights in how it works
  • How I worked with every department in the company to get buy in and all of the information we needed
  • How I managed a full site rebuild as the sole developer

“WordPress is just hacks upon hacks upon hacks”

When I took over development, the marketing site itself was basically just a WordPress theme and a not-insignificant number of plugins to make things work. This isn’t that unusual for a WordPress site: get a theme, add some customization to the theme, and once you need more functionality just throw plugins at it.

If you’re a WordPress developer, you know exactly what I mean.

Over time the needs of the site continued to grow, but like an unstable house our website lacked a solid foundation to build new features and experiences without seriously bogging everything down.

We had tightened up a lot of screws: added git versioning, wrote Gulp scripts to optimize JavaScript and images, wrote Webpack bundling to try and split out as much of the code as we could, and various numbers of other optimizations, while at the same time working to add new pages and refresh designs with the rest of the marketing team.

Perhaps a controversial take, but I find WordPress to be a case study in the phrase “hacks upon hacks upon hacks”.

Many developers know the pain of finding code written seven years ago that most assuredly does nothing, yet completely breaks everything when you remove it.

Our functions.php file was like that, with thousands upon thousands of lines of code the server had to parse through.

The challenges of using WordPress for a massive marketing site

Using WordPress for a marketing site has a number of challenges to work through, but we were also stretching it to its limits. At its core, WordPress was built to be a blogging platform and had evolved over time to try and fit larger scale projects.

Here’s what our whole WordPress setup looked like:

  • Over 300 pages, either created in WordPress’s Pages dashboard or custom coded via page-my-page.php
  • 500-600 blog posts and other types of content as custom post types or taxonomies. This also included online workshop support (3 pages generated per workshop)
    • At the time of rebuild, our content was spread over 9 custom post types, 5 taxonomies, and a lot of custom fields using Advanced Custom Fields
  • ~60 WordPress plugins when I started, which I was able to slowly whittle down to ~25-30
  • ~700 redirects using two different plugins: WP Redirection and Pretty Links

Not to mention everything we built into the theme:

  • TailwindCSS support. This never had been fully adopted, so there was a combination of TailwindCSS, Bootstrap, and custom CSS stumbling over each other
  • Custom Webpacker to bundle up various JS files
  • Gulp tasks running cleanups, minification, purging, and image optimization
  • Cypress for E2E page testing and Jest for some functional JS testing

If it’s not clear yet, let me be clear: Everything was slow.

Because of the infrastructure of our site and mountains of technical debt, pushing an update to the site required 20-30 minutes of build time and test automation.

If someone simply found a typo on a custom coded page, it could sometimes take 30-45 minutes to get a PR set up, make the change, get tests to greenlight, then push the build to production.

It was agony.

Take that timeline and multiply it within new page builds, design feedback, revisions, and the simplest of pages might end up taking several days to over a week to get shipped, simply because of build times.

I’ll say again: it was agony.

What is a marketing site if it has trash SEO? Answer: a bad marketing site.

The purpose of any marketing site is to get people in the door to see and ultimately purchase your product. The mountains of code over time and the intricate nature of how certain things were connected were impacting our SEO heavily.

Our mobile scores ranged between 20-40 out of 100 on Lighthouse, and desktop wasn’t much better. It seemed like Google hated our site, and it was hard to ignore.

No matter how hard we worked at marketing campaigns, our search engine results were so poor that our inbound marketing was constantly suffering. As the person in charge of making our site run as smoothly as possible, I felt horrible that all of the work my teammates were putting in felt like it wasn’t having the impact we wanted or needed it to have.

Couldn’t we just install more plugins? Couldn’t we just optimize what we had?

Yes, but you can only slather paint on a broken wagon for so long before you’re stuck in the mud forever.

It was time for a change.

Toward the end of 2020, we had to make a choice: either we double down on WordPress and try and hack it some more to make it work for our purposes…or rewrite the whole thing from scratch.

Our marketing site required the following:

  • Faster loading times to reduce visitor bounce rate and improve conversion
  • A content management system that all of our teams could use with ease
  • The ability to produce new pages and features in less time by aligning closer with our marketing design system
  • A solid “future-proofed” system that could easily scale with the needs of our growing company

The solution I decided on was Next.js, a framework built on React, and continue using WordPress only as a content management system. This would give us lightning-fast load and browsing times for our visitors, future-proof the foundation of our site by decoupling it from WordPress, and give us much more flexibility and ability to dream up cool new experiences on our site.

What is “headless” and why do it?

One of the early decisions we made was to move away from purely relying on WordPress as a website solution. There are numerous reasons why someone might leave or stay on WordPress, but for us, the reduction of reliance on a single CMS was a good place to start.

“Headless” refers to decoupling the front-end of a website from the back-end content management system. This means that we can use any system, or any number of systems, for managing our content, and as long as the data is retrieved correctly we can simply update our various APIs and

Let’s say you have a page that needs a title, content, and an author. The CMS might send data in a format something like this:

// CMS #1
283674: {
'title': 'My cool blog post',
'content': '<p>This is the content you might find in the blog post.</p>',
'author': 'Doris Middleton'

// CMS #2
'id': '283674',
'post_title': 'My cool blog post',
'by': 'Doris Middleton',
body: {
post: 'This is the content you might find in the blog post.',
format: 'RAW'

And let’s say I have a <Post /> component that receives these data points:

<Post title={title} content={content} author={author} />

// Post.tsx

const Post = ({ ...props }) => (
<h4>Post by - {}</h4>

Notice with this format, the <Post /> component doesn’t care about where the data comes from. It doesn’t care if the data comes from WordPress, Gatsby, or some brand new CMS that came out yesterday. It just needs what it needs, and as long as we can give it that, it is perfectly happy.

This is the power of running in the headless method. We can leave <Post /> alone completely, and manage the data source and data handling someplace else in the app.

In this example:

  • Publish the content via whichever CMS we need and make sure it’s accessible via an API
  • Fetch the data from the API
  • Mutate the data to fit the format we need
  • Use the data to fill out the page or component

So what’s the point? This means that if we ever wanted to move away from WordPress, or add an additional CMS for a specific team, or pull in data from a different API at build time, nothing fundamental about our front-end stack would need to change. We fetch the data, format it, update a few functions, and we’re golden.

I’ll get into more of the specifics of the build in the next blog post about our stack, but first I want to talk about getting the team on board with the rebuild.

Getting the Team on Board

At first, my pitch to rebuild the entire marketing site was met with some resistance. The team was used to working with WordPress, and the idea of moving to a completely new system was daunting. This was a long term project, and we had plenty of marketing campaigns and work to be done in the meantime.

However, I knew that the status quo was unsustainable. The site was slow, poorly optimized for SEO, and changes were difficult to make without breaking something else. It was only going to get worse.

Whenever you want to make huge, sweeping changes to how things are done, especially on a small team, you have to communicate as well as you can. With team support, your job becomes easier, not harder.

First, I communicated the issues we were facing clearly, using data to illustrate our problems. I provided metrics on our slow build times, poor SEO scores, and how these issues were impacting our inbound marketing efforts.

By presenting this information in a straightforward manner, the team could see the urgency of the situation.

Next, I worked closely with each department to get their buy-in and understand their needs and concerns. I took copious notes outlining each department’s concerns and goals, so that I could make sure we were building a site that worked for everyone. By listening to their feedback and incorporating their ideas into the rebuild, I gained their trust and support.

Finally, I presented a clear plan for the rebuild that addressed all of our issues. I explained how we would use Next.js, GraphQL, and headless WordPress as our central stack. I emphasized how this would give us a solid foundation to build new features and experiences without slowing us down.

With this approach, I was able to get the go-ahead from the team to move forward with the rebuild.

Was it worth it?

At the time of writing this post, our new site (internally referred to as Site 2.0) has been live for just over 2 months. Here’s a few positive stats from what we’ve seen:

  • Mobile SEO scores have increased from an average of 20-30 to 65-75 (still work to be done here!)
  • Initial site load time has decreased from 3-4 seconds to ~1.5 seconds on first visit, and on re-visits can be as fast as 200ms for first paint, but this varies based on device and connection
  • Site build time has decreased from ~27-30 minutes to ~2-4 minutes
  • Anecdotally, page and feature development time has seen a massive decrease in time and mental overhead. Alignment of components with our marketing site design system has made development a breeze.
  • Another anecdote, but my own personal happiness and enjoyment with coding has increased tenfold 🥳

So was it worth it? So far, all signs point to “Yes!” There’s still plenty of work to be done and optimizations to be made, but from a foundational point of view, this was a huge success for our team.

It’s also a lot more fun to code again.

This is a big one for me, and I think for many developers. How often do you dread sitting down at your desk simply because you know it’s about to be a slog through technical debt and hacks?

Our codebase had become like this for me in a lot of ways. There were many mornings where I’d sit down and just stare at the monolithic codebase we had constructed and just feel overwhelmed, and it was easy to want to just do stuff the easy way rather than the right way.

This isn’t intrinsically the fault of the framework or system or even the language. It was the result of many years of just “making it work”, rather than following a coherent design system.

With the improvement of overall architecture and a chance to systematize more things, it has become more of a joy to sit and work on the next project or update.

In the grand scheme of things it might seem small, but in my experience better results come from those who enjoy the work.

In the next post, I’ll talk about our overall stack of Next.js, GraphQL, Apollo, and WordPress, and how we use these tools to power the marketing site of the leading marketing platform for creators.