Photo of Marco
Marco
  • Mon Oct 07 2024

Building The New Nimble Ape Blog

Building The New Nimble Ape Blog

As you might have noticed, this blog looks quite different from what it used to be! We recently set out to modernise the blogs for our companies, starting with the Nimble Ape blog. While we’ve been posting new content (more or less) regularly, it’s been a while since we showed some love to the blog itself. This felt like the right time for a redesign.

And it wasn’t simply a new lick of paint! Over the past few weeks, we’ve been hard at work completely rebuilding the blog website from the ground up: new site building technology, new UI, new CMS. Here’s how we did it.

A complete rebuild? Why?

But first of all, why rebuild the site from scratch, when the old one is working perfectly fine?

The main reason is that Nimble Ape has come a long way since we first released the old blog, and we’re not only a team of developers anymore. We need anyone in the team to be able to contribute to content independently, not just devs. The old blog was a Jekyll static site that uses Markdown files to build the post pages. Nothing new, except that if you’re not a developer, your workflow to publish a new blog post looks something like this:

  1. Write the post in Google Docs, formatting it the way you want it to look;
  2. Send the doc to the dev, who will convert it to a .md file, often manually;
  3. Send them the picture files separately;
  4. Wait for the dev to send you a link to the preview build;
  5. Realise that the preview doesn’t look quite like you wanted it, because the website’s design and components are quite different from Google Docs;
  6. Accept there’s little you can do about it;
  7. Wait for the dev to merge to production and make the blog post live.

As you create more and more content, this back-and-forth is just too inefficient. We needed something that would let anyone create and publish the content independently. Yes, we needed a CMS.

On top of that, the rebuild would be a chance for us to modernise the dated look of the blog, and a chance for us to keep up to date with and to leverage the latest technologies to provide a faster, better and more accessible user experience.

The new blog

CMS

Once again, we went for Builder.io, as we’ve had quite a lot of experience using it this year. It offers a Visual Editor where you can drag-and-drop your custom components into a WYSIWYG page that shows exactly what the final build will look like. Developers write the code for the components and the layout, and then anyone can build pages and sections within pages.

I wrote about using Builder.io for the Broadcast Bridge landing page a few months ago, and we also used it for the CommCon 2024 website, so using it again was the obvious choice for us. We still believe it’s not perfect (more on that later), but for now it’s the solution that ticks the most of our requirement boxes.

Static site

When we previously worked with Builder.io, we used Qwik as our framework. We really liked its performance, the “resumability” feature for server components, and its ease of use (reactive state variables!). Qwik was developed by the team behind Builder.io, so it integrates well with their CMS.

Despite all this, we decided to go another way this time. Looking at the medium to long term, we don’t know that we’ll keep using the same CMS forever. Qwik is still a very new framework and not as widely supported as its bigger counterparts, and we wanted a framework that we could plug into a new CMS fairly easily.

Therefore, it wasn’t long before we decided for the universally-supported Next.js. We’d read about the latest advancements in server components introduced by Next 14 with the app router, and it was time for us to get on it too.

So how did it go? Very well so far! Integrating with Builder.io was a breeze, and I found the app router easier to work with than the old page router. The way client side vs. server side is handled just makes more sense to me: Just like Qwik, a component is server rendered by default, unless you specify.

As for the UI (none of us are designers), we went for Tailwind using the pre-built Preline components, with some light customisations where needed.

The static site is deployed and built on Cloudflare Pages. Pro tip: Save yourself some pain and use next-on-pages as your build command 🙂.

On Builder.io, we set up a webhook that triggers a new Cloudflare build whenever new content is published or updated. This enables whoever creates the content to make a blog post go live without the need to have access to Cloudflare.

All the blogs

While setting up the work of building a new blog website, we decided to go the extra mile and make the new site a basic structure that we could reuse to deploy the blogs of our sister companies, which are also due for an update fairly soon.

The code for the blog websites is a scaffold that lives in only one repo, which can be deployed to different Cloudflare pages. During the build, we use environment variables to specify what brand we’re building for, which Builder.io space to use for content, and what branding theme we need to apply. We make sure that all the content, including logos, headers and footers, lives only on the CMS.

Here’s an example of how we use the Next.js app router to specify the site’s metadata and favicon:

// layout.js

export const metadata = {
  title: process.env.WEBSITE_TITLE,
  description: process.env.WEBSITE_DESCRIPTION,
  openGraph: {
    title: process.env.WEBSITE_TITLE,
    description: process.env.WEBSITE_DESCRIPTION,
    images: [process.env.WEBSITE_IMAGE_URL],
  },
  icons: {
    icon: process.env.WEBSITE_FAVICON_URL,
  },
};

We’re confident that doing all the groundwork now will mean a much easier job upgrading the other blog sites in the future!

Challenges

There were a few bumps along the way… there always are!

Domain proxy

While the blog is an entirely new website, it should appear as a subpath of the main nimblea.pe website. To accomplish this, we deployed a Cloudflare worker that is accessible on the nimblea.pe/blog* routes and proxies the request to the actual blog’s URL.

One key trick here: to make sure the Next.js website is displayed correctly, add the nimblea.pe/_next* routes to the worker!

Since we changed the URL structure from the old blog, which was found at nimblea.pe/monkey-business*, we created a _redirects file that Cloudflare can use to redirect the old URLs that have been published in the past to the blog’s new home, so the content can still be reached.

Make Builder.io’s Visual Editor work with the static site

This is always a bit tricky. The Visual Editor needs a deployed website that defines the custom components, the page layout and the Builder.io integration. We did this by creating a /preview route that would be used specifically for this purpose, and would be inaccessible in production.

The new page needs to be rendered on the client side, to play nicely with the Visual Editor. To protect it from accidental navigation, we require a token in the URL search parameters:

// app/preview/page.jsx

'use client'
import { redirect } from 'next/navigation';
import { RenderBuilderBlogSection } from '@/components/builder';
export const runtime = 'edge';

export default function BlogPreview({ searchParams }) {
  // Basic check here to prevent unauthorized access
  if (searchParams.token !== process.env.NEXT_PUBLIC_PREVIEW_TOKEN) {
    redirect('/404');
  }

  return (
    <div className='mx-auto'>
      <RenderBuilderBlogSection model='blog-section' />
    </div>
  );
}

In the Blog component’s settings in Builder.io, set the preview URL including your token.

Applying brand themes

As I mentioned above, we use Tailwind for our UI, but we want to customise the theme during the build using environment variables.

The best solution we’ve found so far is a bit of a hack, one that leverages HTML data-* attributes and CSS.

We defined one CSS file for each brand, for example:

/* themes/nimbleape.css */

html[data-theme='nimbleape'] {
  --primary-color: #FEA70F;
}

All the CSS theme files are then imported into the global.css file, and CSS variables are used in tailwind.config.js to extend the default theme.

In layout, we can then apply the correct theme conditionally using environment variables:

// layout.js

export default function Layout({ children }) {
  return (
    <html lang='en' data-theme={process.env.NEXT_PUBLIC_WEBSITE_THEME}>
      // ...
    </html>
  );
}

This approach has some advantages, mainly its simplicity and the fact that we could potentially define very detailed themes for each brand, just by building out each CSS file. The main drawback, however, is that we are including all the brand CSS files in each build, and sending potentially a lot of unneeded CSS to the browser.

Therefore, while this solution works for now, we want to improve on this soon.

What’s next?

On top of brand themes, these are a few other things to be worked on down the line.

Staging environment

This is one of those things that is normally easy to do, until you need to make it work with the CMS. The Visual Editor gives you a great preview of what the final blog post will look like, but it’s not quite the same as clicking through the final build. We want to be able to build a full preview site of the new content before it goes live, as well as new features that we might be working on.

Yes, Cloudflare Pages give us preview builds. However, Builder.io currently only offers content environments to enterprise customers, meaning that for us there is only ever one version of the content. This becomes a limitation when you want to publish your content for preview, but you’re not quite ready for it to go live.

The way we’ve addressed this in the past is to make the Builder.io publish webhook only trigger a preview build on Cloudflare. To deploy the new content to production, you need to manually trigger a production build. This works well, and avoids the risk of accidentally publishing content before it’s ready, but comes at the cost of removing the ability to go live directly from Builder.io: A developer is always needed for the last step of deployment.

We’re still looking for the perfect solution – we’ll post an update when we find it!

Include images in the build

Currently, the images added to the content through the Visual Editor are served from the CMS. Ideally, we’d like to keep control of all of our content, so images should also be part of the build, and served statically with the rest of the site. This guarantees that the website is built completely separately, and that it will always be available even if Builder.io is down.

This is something we’re currently working on, as it presents some challenges. We can download all the image files from Builder.io during the build process, but the issue is: How do we replace the src for each image component in each blog, and how do we align each URL from Builder.io to an asset in the build?

We’ll post an image-rich blog post when we find out!

Conclusion

Over the course of this year, we’ve shown that a visual CMS really opens up doors for content management in mixed teams like ours. We want control on the website and its components, but we want to let anyone in the team have ownership of the content.

We’ve also learned that this doesn’t always come easy, and tailoring a CMS to our specific needs often presents challenges. While we’ve found solutions and workarounds for most of them, we’re hoping to see more flexibility built into Builder.io and other visual CMSes as they develop and mature.

Hopefully sharing some of our challenges will help other teams like ours to get started on their visual CMS journey!

Need help?

If you’re working with streaming media technologies and need some extra support, we’re happy to help. Nimble Ape has been consulting on real-time media projects for a decade, building products for clients big and small all over the globe.

Why not drop us a line on contact@nimblea.pe.


- Marco and the Nimble Ape team

BlogNext.jsApp routerBuilder.ioCMSHeadless CMSVisual CMSvisual editorTailwindPrelineRedesignBrandingNimble Apesmall team