In this article, I will explain the methods we used to build a white label product where we can override most parts of the product with theme/client specific pieces.

Photo by La-Rel Easter on Unsplash

Give me some context — Why?

I work on the project Schul-Cloud at the Hasso Plattner Institute in Potsdam Germany. There we are developing an educational cloud for the German school system. In Germany, each federal state is responsible for its education, so many states want their very own education cloud. This means for us to exchange logos, adjust colors and partly also adapt the content of whole pages. Sometimes we even develop features which should only be available on some platforms.

The Concept

example project directory structure with theme directories

To ensure this adaptability we have developed a special build process and directory structure. Our main code is centrally located in one place. Each theme (a special version for a state) has an additional “theme folder”. In this folder the whole folder structure can optionaly be mapped again. When a build is created, all files were loaded from the theme folder if available and the main project is used as a fallback.

Let me show you an example:

If we have a file /src/components/Logo.vue, it will be replaced during the build for the “THEME-A” theme by /src/themes/THEME-A/components/Logo.vue (if the file exists).

The Problem

This concept has been working very reliably for us for some time now. However, we are currently converting our frontend architecture from a gulp based build process to Nuxt.JS (which is using webpack in the background), which is why we would like to adopt this concept in Nuxt.JS. It turns out, it’s not that easy because NUXT.JS is giving you a lot of great defaults you have to workaround.

Prerequisites — Repo Setup

At first, I would like to explain how I have initialized the repository.
To get started I executed the nuxt.js bootstrap command npx create-nuxt-app <project-name> and then moved all the created directories into an src directory to keep the repo organized. This also requires setting the srcDir key to “src/” in the nuxt.config.js file.

That’s it. All the options during the setup are up to you.

Let’s theme components!

To be precise: Let’s start with theming components from the /src/components directory with overwrites from /src/themes/{THEME}/components (again, if they exist).

The solution I will present now will rely heavily on aliases. If you don’t know already, Nuxt will provide you with the aliases @@ and @ to import components as an alternative to relative components. We will extend these aliases by adding an alias @components. Whenever you then import something from the component directory you need to start the import with @components instead of relative import paths. If you are not willing to follow this convention I have to say sorry, the concept I will present here will not work for you.

Create Aliases for Components

At first we will create a file called alias.config.js at the root directory. In this file we will define all our aliases. We then need to add the exported aliases to our webpack build config in the nuxt.config.js file.

We can check that this works by adjusting the import in src/pages/index.vue from import Logo from "~/components/Logo.vue" to import Logo from "@components/Logo.vue".

Overwrite components

The trick we will do now is, to generate aliases for all components in the theme directory and push them into our alias config. Webpack will then resolve the more specific alias first and therefore use the themed component instead.

In order to achive this, we first need to get a list of all files in the themed directory. Then we just need to create the aliases, merge the objects and we are done. It is that simple.
To get the list of files I will use the glob package that you can install using npm i -D glob. The only important thing is, that all operations need to be synchronous because we still need to export the alias. But this shouldn’t cause any performance problem, because this code will only run once during the application build.

Now you only need to set the THEME environment variable and restart your nuxt application to make use of the themed files. You can test it by creating a file at src/themes/THEME-A/components/Logo.vue and build the project with THEME=THEME-A set as an environment variable .

Overwrite Assets

Of course, your project does not only contain components. There propably are a couple of images, icons, fonts and so on. To theme those, we can use a similar approach than for components.

We just need to extend our alias list to include assets as well and use alias imports wherever possible.
That’s it!
You can also find all the required changes in this commit 1fc5366.

Image for post
Image for post
required changes in aliases.config.js

Usage example of the new @assets alias:

<template>
<img class="icon" src="@assets/icon.png">
</template>

Let’s theme whole pages!

To theme pages, we need to dive a bit deeper into the Nuxt.JS world. The idea how to do this was in my mind for a couple of months now, but I haven’t had time to test it. Luckily Israel Ortuño provided me with a nice short example which I can now share with you.
The idea is, to extend the routes generated from nuxt and replace the component path if we can find a themed version of a page.

To do this, we just need to import the node fs module and extend the nuxt.config.js.

import fs from "fs";export default {
// all the existing stuff from nuxt.config.js
// ...
/*
** themed pages
*/
router: {
extendRoutes(routes, resolve) {
const theme = process.env.THEME || "default";
if(theme === "default"){
return
}
routes.map(route => {
const path = resolve(`src/themes/${theme}/${route.chunkName}.vue`)
if (fs.existsSync(path)) {
route.component = path
}
return route
})
}
}
}

But what about …?

Layouts, Middlewares, Plugins, the static directory and most importantly the store? How can I theme those?
Unfortunately, I didn’t had time yet to research and document theming methods for these directories. But I will do, so stay tuned for part 2 of this article.
If you want to follow my progress, take a look at this GitHub Repo: https://github.com/adrianjost/nuxtjs-theming

In the meantime, I would like to know how you tackle such theming problems at your company?

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store