How we used Vue.js to create a single-page application with django CMS

This is a two-part write-up on how we managed to create dreipol.ch as a single-page app with the help of Vue.js and django CMS. Make sure to read our first part, written from the perspective of our backend team!

Rouven Bühlmann
dreipol

--

Visit www.dreipol.ch to learn more about us.

When looking back at the history of our projects, they can be grouped into two different categories. The first one being fancy single-page apps with a high density of user interactions and the purpose of swiftly using a service or product. The second one being information-rich company websites with the primary goal of providing content in a fast and accessible way.

So far, those two types were mutually exclusive as their core technologies served very different purposes. We’ve been using Vue.js for feature rich single-page apps since late 2014 now. Our usage of django CMS for data heavy websites dates back even longer. Ever since our frontend team felt the new opportunities possible with Vue.js, we felt the need to combine its power with a mature content editing solution like django CMS.

The inline-template approach

Our first attempts of doing so included leveraging Vue’s excellent inline-template feature that helps closing the gap between server side templating and the framework’s own templating mechanism. Although the setup basically worked, we realized: The code soup created within the Django templates was something we didn’t want to maintain. Nevertheless it’s still a great solution for old projects that otherwise wouldn’t be able to adapt to a modern frontend framework.

Going full SPA

When we started working on our new company website dreipol.ch, we knew that the time to try out a new approach had come. Our team decided to take a dive into new territories and put some time into researching on how we could manage to avoid page reloads and leverage the full power of the Vue.js eco system consisting of its core modules Vue itself, vue-router and vuex.

Decorating vue-router

Usually, in a single-page app, all route definitions are stored on the client. With a CMS as a content provider, this is not feasible. As you’ve already read in part one, our backend provides us with a full list of available route definitions baked into an attribute within the page’s html tag.

The route object without our html

The route config object is used as a template that is being hydrated by our code before being fed to the router.

Our route decorator function takes this config object served by our cms

The transformed object looks like this:

An example router config with one route after transformation

There is much going on under the hood. The first thing you might notice is the new object called meta. We need to sneak in a few things into the route object and make sure they’re available later on when we actually change routes. Due to vue-router’s internal mechanism, all properties that it doesn’t understand are being stripped out of the object. The meta property is intended to store arbitrary information.

Within the api object we’ve added a new method that handles data retrieval and storage for us so we just need to call it and wait for the returned Promise to resolve.

Something that’s gone in the transformed object is the fetched property. It had stored route data, that we’ve transferred into our internal store now. This speeds up initial page rendering, as we avoid an additional fetch request to get the route’s data. As the server is aware of the page it is rendering, it only populates the fetched property for the route that is initially needed.

The other two props called params and query are used for dynamic route rendering. This is the API counterpart of the same system in vue-router’s link handling mechanism. We’ll discuss this later on.

CMS structures and their counterparts

When rendering a django CMS page with Vue.js, we felt that the best solution would be to simulate the well known CMS system with Vue.js components. This has proven to be a good pattern, as it also helps implementing one of our most favourite CMS features: Frontend editing.

Page templates
To start with, every page template has a Vue.js counterpart that is being referenced in a route definition. Even before the route definition object is fed to the vue-router we replace the component attribute with the actual Vue.js component that is being rendered.

Placeholders
Being the main building blocks of a page, the placeholders are added into our data structure within the containers object:

Two placeholders within our fetched object

Plugins
Every plugin is mapped to one Vue.js component that expects the same data the plugin would render in a normal CMS setup. All data is stored in the content object. You can see in the example above where the next snippet comes into place.

The plugins array containing an image plugin as an example

Static placeholders / generics
The trickiest elements turned out to be static placeholders and generics, as well as template tags like the `menu`. As those elements are identical on several pages, we didn’t want to load them with every request. We included them by using a query parameter called `partials` which holds an array of partial names that the server should send us. With a separate store for `partials` we could keep track on which partials we’ve already received and which ones are still needed for the rendering of the requested page:

The partials object with a menu partial in it

The route mixin

In order to automate our data fetching system, we’ve created a route mixin that is part of every component representing a route instance. To trigger a fetch we could use the two lifecycle hooks beforeRouteEnter and beforeRouteUpdate. What happens is this:

Fetching via vue-router’s two lifecycle hooks

Fetching all the things!

So far, we’ve circled around the most important part of the whole app: The data fetching! When creating the consumable router object we add a fetch method for every route. The function goes through several steps before eventually returning a promise that resolves with the necessary data:

  1. Merge the initial (static) params and query with the route’s current params and query
  2. Compile a final URL out of the path, params and query
  3. If an optional response parameter is present, store the data under the compiled storage URL
  4. Finally, return a promise that resolves with the route’s data. Its origin is either an ajax request from Axios or a store lookup with the compiled URL as a key
Simplified version of the method that is creating the fetch functions at startup

Storing all the things!

When it comes to storing the data you’ve previously downloaded, it’s really up to you how to implement it. We’ve opted for a vuex solution, as we’re using vuex in most projects, anyway.

To keep the rest of the application free from page fetching logic, we decided to also handle accessing store data with vuex actions. This is a bit unconventional, but seemed to be the cleanest solution to us:

Vuex action ‘getRouteData’

This method returns a promise, either directly or via Axios, that resolves with a success value of:

{
"partials": {},
"data": {}
}

The received partials, as well as the content of the data property get stored into the cache with a dedicated URL as a key to retrieve it, should it be necessary.

Dynamic routes

Let’s say you have a route that shows a user profile in /user/:username. You will need to provide a param username to fill in the gap when you want to link to a certain user profile: <router-link :to="{ name: 'profile', params: { username: 'johnsmith' } }”> John Smith </router-link>. The params (along with the query data) will be available in the route component under this.$route.params. When navigation occurs, a fetch request is triggered and the current params and query data will be used to manipulate the API endpoint. So a request to api/de/pages/user/:username will turn into api/de/pages/user/johnsmith.

Conclusion

The last months and years showed us that the web platform doesn’t stop evolving. Progressive web apps today, if done correctly, feel reactive, smooth and motivate users to spend more time and return on a regular base. Our successful experiment with our own agency website showed us, that also seemingly conservative projects can profit heavily from features like dynamic content loading via ajax.

Perceived loading time has dropped dramatically due to several reasons. Django’s template language has always been a bottleneck to page rendering and has consciously been held poor on features. Now we’ve not only improved the speed of our websites but also won many templating features by fully embracing Vue’s template engine. Because a full page reload isn’t necessary anymore, a big part of our page doesn’t have to be touched when navigation occurs. This is less intrusive for the user and also helps save time until the page is interactive again.

As the own website is always a special project within a company, we’ve tried out our approach with a feature-rich and large django CMS website that we could build from scratch. Apart from some necessary evolutions, we could prove that our concept works for any feature that django CMS supports. And, as we’ve managed to stay within our budget, there’s no reason for us to stop building pages like that.

Our next step will be to create an npm module out of our frontend production code, so others may profit as well from our approach.

Stay tuned!

. . .

Feel free to like this post, share it, follow me or dreipol on Social Media:

medium.com/@nirazul
twitter.com/dreipol
www.dreipol.ch

--

--