I've written about our decision to build our own CMS and how we created our ideal content structure. But, what does that actually mean and what are the building blocks?

  • A headless CMS
  • Static site(s) generator
  • Multiple environments and destinations
  • Dynamic content sprinkled through out

A headless CMS

For our team, this translated into a Vue application that connects to a Laravel backend via GraphQL. Although there are plenty of opinions about GraphQL and APIs, the excellent Lighthouse package made setting up and using GraphQL extremely easy. By building the backend of the CMS as an API-first application, anything we want to do from a different UI should "just work." We already have plans for this exact use case later this year.

Static site(s) generator

Since we're in the Laravel ecosystem, it made sense for us to look into Tighten's Jigsaw for building our static sites. As I mentioned before, we were really curious about Statamic 3 and it's potential capabilities. In the end, Statamic didn't quite fit our needs (and V3 wasn't quite ready).

Since we built the CMS to be headless, Jigsaw made it extremely easy to just fetch content, create remote collections, and build pages. We also use remote collections to fetch site settings. Things like navigation, logos, and which dynamic content is enabled.

Multiple environments and destinations

While our team is working on a site, we wanted the ability for them to preview content before publishing. Again, an API-first backend makes this extremely easy. When we are editing pages, we have the following workflow:

  1. Save the page content from the UI (each save is a new version in the DB which lets us rollback if needed)
  2. Trigger a build in Jigsaw
  3. Serve a preview of that build at a preview url
  4. When ready, publish the site to it's final destination

When we publish sites, we are triggering a Jigsaw build marked as production and then moving those static files to a separate server cluster.

Dynamic content sprinkled throughout

Since Jigsaw compiles everything down to static HTML, we needed to do some work for dynamic content. Initially, we built a library of Vue components that could fetch dynamic content when needed. However, we have whole sections of sites that rely on dynamic content. In other words, from a certain URL down, we were essentially using a Vue application for routing and rendering.

The biggest trouble here is that we really wanted our meta + OG content to be available on the page and not rendered through Vue. Even with the Vue Meta package, our content never came through as expected in Facebook or Twitter.

Jigsaw's event listeners offers us the chance to sprinkle some PHP back into our pages. I'll cover this more in an upcoming article.

Conclusion

There are a lot of moving pieces for our workflow. A Vue application for the CMS UI, a Laravel application for the CMS API, a Jigsaw instance for generating static files, and some server magic for serving both preview and live sites. It took some time to build out the infrastructure, but we're confident we'll be able to scale as our needs and user base grows.