Preact meets CMS: Building Lightweight Portable Widget Components

A Content Management System (CMS) is one of the very first tech investments a lot of companies make, and no matter how large the business is or what kind of CMS they choose, product & marketing teams need an effortless and safe way to author new components with their ever evolving ideas in order to serve their customers better and deliver more value.

Traditionally, this is done by building those components as styled Java or PHP templates depends what CMS you have, which I believe is still the best way to deliver most of the typical content blocks where there’s lot of words and images and a minimal amount of UI interactions, but when a CMS component is rich with UI interactions building that in vanilla JavaScript with the server rendered HTML can be a little tedious and modern JavaScript UI libraries like Preact, React, Svelte, Vue can actually be a better way to tackle this problem and improve the Developer Experience, Productivity and Testability.

The Advantages of Client Side Widgets

Building a client side widgets and deploying it as a static file make a lot of sense in some use cases that look similar to those:

Rich UI Interactions

If a widget is big on fetching data from external or 3rd party APIs and has a very dynamic interface and possibly client side data filters, dropdowns, sliders etc.. utilising a modern UI library will keep you highly productive by providing a functional approach to build and reuse elements, also by giving you a bunch of compatible off the shelf open source modules to import and use or just get some inspiration from.

Domain-wide Reusability

Client side widgets can be served through your CDN and can be reused not only across the CMS pages, but also across the business’ different websites and web apps.

Lazy Loading

CMS pages are rendered on the server and that gives us a quick fast first paint with ready content, loading client side rendered widgets asynchronously that will progressively enhance the page without being a blocker.

Fast Independent Releases

Releasing a feature or a fix to my employer’s CMS for example takes a lot of time as there are release slots you have to check and book one a few days in advance, while a client side widget deployed as a static file and served over the CDN has helped us deploy as much and as often as we wanted.

Build in Isolation and Leverage Modern Tools

Some enterprise CMS are a few years old, and it’s very common to see a lot of JS files, grunt tasks and the answer to what’s your existing front-end architecture question is “No architecture really, just jQuery and stuff”. Building in isolation with all what modern tools has to offer such as hot module reloading, es harmony modules tree shaking is a win for both of the developers and the project.

Utilise a Shared Component Library

There are few popular Open Source frameworks like Storybook and Styleguidist for building, documenting and showcasing a set of elements and components that can be reused across all your widgets.

Progressive Widgets Architecture (PWA) with Preact

There are many reasons that make Preact an ideal library to build those widget, some of them are:

Tiny Library Size (Portability)

Comparing Preact library size to moment.js and the fetch polyfill

Preact’s tiny size will leave you a lot of room for your app’s code even when it’s bundled in your widget. It is only 3Kb, just around the size of the fetch polyfill.

Preact leaves a lot of room for your app’s code

Great Performance on Mobile

Preact provides some very satisfying results when testing and benchmarking its performance on mobile devices where JavaScript libraries usually struggle the most. Preact is light weight in size and engineered as a browser-first library, where there is minimal abstraction and no browser events rewrites, all those combined have helped Preact reduce the overhead and improve the performance for mobile browsers.

Size and performance are quite important as the CMS host page might be fast or slow, might contain a ton of other JavaScript, render blocking tracking scripts, etc.. so providing a widget that is as light and as fast as possible is a critical key in progressively enhancing the CMS pages and the whole experience.

Easy to Pick Up

It doesn’t matter if a frontend developer is experienced in P\React or not, what makes picking up Preact fairly easy is the identical ES6 API to React’s API and lifecycle methods (up until latest stable) and there are some excellent blog posts, examples and tutorials developers can learn from.


Preact ecosystem has a lot of packages and modules to support a lot of common needs, it is also worth mentioning that you can utilize all react ecosystem modules and they will be fully compatible with your preact code via preact-compat(PS: preact-compat will add 4.8 KBs).

Rendering in the CMS Page DOM

Looking at this simple widget as an example, you can easily tell we need to give ourself a better API and more flexibility:

import { h, render, Component } from "preact";

class HelloWidget extends Component {
  render() {
    let time = new Date().toLocaleTimeString();
    let name = || "nobody";
    return <h1>{`Hello ${name}, time is: ${time}`}</h1>;

// render an instance of Widget into <body>:
render(<HelloWidget />, document.body);

And Then There Was Preact-habitat

Preact-habitat is just an extension to Preact’s render function where I decided to provide more API. The concept of Preact habitat is pretty straight forward and can be summarized as

  • Scan the DOM when the widget’s script is loaded.
  • Re-scan again when DOM is fully loaded.
  • Allow host CMS page to pass props via HTML.
  • Stay < 1KB.

See the Pen Preact-habitat demo by Zouhir (@zouhir) on CodePen.

Preact-habitat Implementation Details

Walking through the implementation details of preact-habitat, here’s a code snippet for our simple HelloWidget component with an option I have exposed inline the widget and mount it inside the script tag’s direct parent

import { h, Component } from "preact";
import habitat from "preact-habitat";

class HelloWidget extends Component {
  render() {
    let time = new Date().toLocaleTimeString();
    let name = || "nobody";
    return <h1>{`Hello ${name}, time is: ${time}`}</h1>;
let helloWidgetHabitat = habitat(HelloWidget);
  inline: true

// assuming your build tool exports a `hello-widget.js` static file.

Now that we have option inline set to true, to integrate this widget in your host CMS all we need to do is… use the script!

<!-- container with specific styles -->
<div class="container">
  <script async src="cdn.../hello-widget.js"> </script>

There are few other API options like like `clean:true` in case there was a default loading animation provided by the host CMS and you want to clear it before the widgets mounts:

  inline: true,
  clean: true,
<!-- container with specific styles -->
<div class="container">
  <div class="loading-animation">
    <!-- habitat will clear this animation when widget will mount -->
  <script async src="cdn.../hello-widget.js"> </script>

Passing Props, Preact.h Trick & Render Function

The host CMS page developers will certainly love to be able to pass down some custom data that are relevant to that specific page, in preact-habitat all you need to do is add a script tag of type text/props with a valid JSON in it, just like the following:

<!-- container with specific styles -->
<div class="container">
  <script type="text/props">
      "name": "Zouhir"
  <script async src="cdn.../hello-widget.js"> </script>

The first step of implementing this feature from scratch is getting the text/props script’s content and parse the JSON to JS object

let script = hostElement.querySelector("script");
let scriptType = script.hasAttribute("type") || null;
let props = {};
if (scriptType && scriptType === "text/props") {
  try {
    props = JSON.parse(script.textContent); // logs { name: "Zouhir"}
  } catch (e) {
    throw new Error(e);

What makes passing the JS Object pretty easy is Preact’s pragma h function that accepts 2 parameters, first one is the function or ES6 Class component we want to render and the 2nd one is you might have guessed, the props! (You can learn more about Preact’s h function here)

import Preact from 'preact'
let props = { name: "Zouhir" }
Preact.h(HelloWidget, props) // this is equal to: <HelloWidget {...props} />

and the previous snippet is what I have utilised in preact-habitat as the following:

import Preact from 'preact'
// props: already existing variable contains props from host dom
// hostElement: is the div we want to render in

Preact.render(Preact.h(HelloWidget, props), hostElement) //TADA! 🎉

Thumbs Up

Rewriting is expensive and progressive enhancements are a good way to go around improving both of the user and developer experience, there are few ideas I look forward to bringing later this year like rendering in the Preact widgets in a shadow root which offers a pretty neat CSS encapsulation, but since Shadow DOM haven’t landed in all major browser and the polyfills are a little expensive I chose to wait a little longer, another thing you might want to look at if you are going to use Preact widgets extensively in your project is dynamic widget imports via webpack contextual require, this is a very neat trick Jason Miller has introduced to me once and I believe it is quite useful.

Some resources I recommend looking at:

Follow me on twitter or star preact-habitat on Github if you would like to keep up to date with my latest work on this!

Thank you for reading 💜.

Leave a Reply

Your email address will not be published. Required fields are marked *