Having suffered worked with many popular Content Management Systems (CMSs) over the years, I thought I would try my hand at creating my own, from scratch. Not just any CMS, but a CMS that throws the existing concept of what a CMS should be in the bin. This article serves as a brief overview of the core concept I have implemented and I hope to hear your views on what I’ve done in the comments below.
I've reached that stage in my programming career where I have gained a lot of experience building websites using different CMSs, languages, methodologies, frameworks and so on. For a long time now I've been experimenting with ways to get around common problems that seem to occur again and again when building and maintaining websites using popular CMSs of the day. About a year ago I decided to commit to creating a CMS that eliminates these problems by taking a radically different approach. In this article I detail the different approach I took and the various tools I employed.
A Different Approach
To begin with, I made the decision that this CMS will be built (at least initially) for use by people that are skilled and knowledgable when it comes to web development. Contentious perhaps, but with that stroke I've just removed a whole load of what I view as unnecessary complications that would cloud my judgement and would most likely lead to me writing a CMS that is just like every other CMS on the planet.
So, to be absolutely clear, I wanted to develop this CMS on the premise that the construction and maintenance of a website (in its entirety) built with this CMS, should rest squarely on a developer. No designers. Barely any client direction other than receiving content (no instructions on how to present the content). Just a developer, creating an entire website themselves. In the same sense that you might pass an artist some kitchen rolls and glitter and ask them to make some artwork.
My (vague at this point) approach to making this a viable proposition was to create a system that is both component based, in the same vein as Polymer and Vuetify, and data orientated. I wanted to create an underlying schema that describes a component in a similar fashion to how HTML is used to describe an element on a page. I want a system to which I could say:
Display a nav bar.
And the nav bar component will look for data in the schema and render itself based upon what it finds in the schema. It finds a taxonomy, it displays it. It finds some classes, it includes them. It's opinionated, but you can override its opinions should you need to.
Next step, I needed to experiment and (hopefully) prototype this to prove to myself that it is a worthwhile concept.
Writing Components Using VueJs
I set about writing various components using VueJS. I’d learnt a trick that involves iterating over components using what the VueJS docs term a dynamic component.
<component
v-for="(item, key) in items"
:key="key"
:index="key"
:is="item.type"
:opts="item"
>
</component>
If you haven’t encountered this before, we’re iterating over an array of objects that each have a component type property. The value stored for item.type
would relate to a component I have written. So item contains the type, let's say “navBarComponent” and it also includes data that the nav component will want to render, such as menu items and perhaps classes that determine the styling. The whole data object gets passed as a prop :opts="item"
. There, you have it.
Something else that I learnt about VueJS is that, when an object key doesn’t exist, VueJS simply doesn’t render the property. It doesn’t halt execution. This neat characteristic removes the need for endless conditional statements you often find in CMSs.
These two things combine to allow for dynamic rendering of components that can mutate based upon the data made available to them.
Next, I needed to define a schema. I wanted every component to adhere to a standard organised structure. I had one field already: the component type. I would need to consider how the rest of the schema should be structured, which is no easy task given that all the components would adhere to using the structure I decided upon. And what about storing the schema? If I was to store the data that defines the page against a route then it would lead to data coupling. I needed to decouple the data from the route so that I could use the same component on several pages without needing to update each individually. Simple fix: store the key against the route and construct the data assigned to a route when the route is requested.
After much deliberation, I came up with this concept:
User navigates to a route such as /home
.
A request is sent to the app's front controller which queries “home” and returns the key(s) that relate to the data required for this route.
[
{
"key": "home-hero"
}
]
I loop the array of objects (containing only keys) returned from the query, replacing the key with the fully qualified object.
[
{
"key": "home-hero",
"type": "s-hero",
"title": "We are Stellify",
"classes": {
"herosection": "is-dark",
"herosectionheight": "is-large"
}
}
]
I pass this data to the view as JSON.
I feed the data to which will iterate over the array of objects rendering out the components they represent and...
Result!
I set to work on making more (complex) components, experimenting, testing various scenarios. I figured that within a component block there’s no reason why you couldn’t use <component>
again to loop sub-components, if required. So I had what felt like a powerful tool for automating the rendering of content on a webpage with relative ease. Better still, I felt like I’d opened a whole raft of potential.
At this point I shifted my focus to how a developer would build a site using this concept. What I did next was unplanned, I just found myself doing it. I coded VueJS components that would build data objects that adhered to my schema. I built a form builder component for example, I thought that was a good challenge to see if this concept stacked up. It’s a visual builder but the end result is a simple, human readable scheme that defines a component and its associated data. When coding this I realised the need to be even more abstract so I went on to build a general component builder using components from Bulma and (once I had discovered it) Buefy in order to speed up my prototyping and testing. Upon selecting your component type (depending on the type you choose) you’d be presented with various fields that relate to the type (you can also dynamically add your own fields if you wish). The schema could afford to be reasonably loose in the sense that ultimately, the components would only use the data they are programmed to use. Missing properties wouldn’t break anything, additional properties would be ignored until the component was developed to look for and make use of them. To reiterate:
You choose your component type.
You build your component data.
You save your component.
You add your component key to a route.
You request the route.
You receive a response containing a JSON representation of the page and its data.
And that is how you build a website using my CMS.
Making My Code Platform Based
The next job was to work out how to make this platform based. I already knew I wanted to use PHP/ Laravel on the server. Some quarters were advising I use a JS/node framework given that the “app” is predominantly JS based. I didn’t like this, I mean I love JS and people are doing very cool things with node but I don’t see it as language suited to server-side scripting. Plus, I was already familiar with Laravel. I knew that it offered enourmous flexibility in relation to how you set up a project and I’d been researching how best to structure a Laravel project using packages so that you have a true platform, a codebase that can be used by as many sites as you wish. So I went ahead and set up a package, moved all the assets that were not “project specific” into the package and pushed it to a repository.
To cut a long story short, with the help of a Mexican wizard:
I wrote a development and production Docker workflow. The production setup uses GitLab CI/CD to build and deploy the app using containers. The script pulls in the codebase as a package and the outer Laravel install contains all the files that are project specific such as environment files, css/ sass assets and so on. My next steps involve migrating to using Kubernetes, investigating how I could allow sites to be build via an API and trying to sell this idea to people like you, yes you, the person reading this article.
So have I written the perfect CMS? Probably not, but from my perspective I’ve certainly done something a bit different, albeit I haven’t exactly become a trailblazer. I’ve really just leveraged ideas, practises and technologies that are very much prevalent across the world of web development at the moment and I’ve done this in quite a methodical, structured way that seems to work very nicely. One thing I pleased about is how this setup hasn’t slammed any doors shut. I feel like I have all options still available should I want to use them. I’m still able to use all aspects of Laravel’s framework and ecosystem and I’m still able to pull in JS plugins. Thanks to Sass variables sitting outside of the package containing the components, I can style websites built on top of the platform to make them look distinct from one another. The only thing I know I’ve broken for sure is the ability for SEO software to analyse the source (this could be fixed using pre-rendering but I’ll leave that to someone who cares about SEO software! Google can render and crawl the pages, job done!)
I didn’t write this article to give my verdict however, I wrote it for you, the reader, to give your verdict. So what do you think? There's a lot of other things I could discuss but I'll see whether people are interested before continuing with a second article.
P.S. I have a site built using the platform up on the WWW: https://stellifysoftware.co.uk/ check it out!
Top comments (2)
Great post Matt! Any chance you have a skeleton version of the code to link so that I can check out how they all fit together practically?
Hi Leo...three years later...github.com/StellifySoftware/prevue