Lightweight and High Performance Drupal Themes with Modern Tools
The web development landscape has seen a resurgence in support for high-performance, server-first rendering strategies. With the release of single directory components in Drupal and the maturity of related “not invented here” toolchains across the landscape, Capellic is excited to catch the big wave of modern technologies making lightning-fast and easy-to-maintain Drupal themes a reality.
At Capellic, our dev team is really interested in constantly learning and evolving our practice. While we have many and varied interests in terms of what new developments really spark our curiosity, there are some patterns to things that repeatedly seem to come up in our internal dev discussions. We often discuss improvements in tooling, in the practice of how we develop websites. Sometimes this is looking at things like cloud development environments, it’s discussing new AI-Agent-based tools like Cursor and Claude can help us write code, but at a much more integral level, we often discuss the nuts and bolts of the stack we use to construct our projects.
Capellic has long used the varying iterations of component-driven theme frameworks to build our client sites. Every dev team member fully understands the monumental shift that component-driven design and development was to the Drupal theme development space. We would never dream of returning to the days of more page-driven theming.
First Capellic adopted PatternLab when it exploded onto the Drupal scene thanks to the great work of Brad Frost in codifying the Atomic Design system. In those days the pattern library was built separately from the Drupal theme, and parity between them was a painstaking manual process. PatternLab was an important step in the conceptual evolution of theming in Drupal, but it was more or less superseded by our next tool.
For several years Capellic has been building client projects using the Emusify system developed by Four Kitchens. Emulsify solved the problem of complete separation between the pattern library and Drupal by running both contexts through the exact same template files and shimming the differences between Drupal rendering and pattern library rendering.
Late last year we set out to refresh our theme stack for a few reasons. First, we wanted to leverage changes in Drupal core such as the inclusion of single directory components (SDC). Second, we wanted to simplify the stack for higher performance (both in development and at runtime). Third, we wanted to significantly lower the maintenance burden by reducing dependencies.
Hyperdrive
With all of this in mind, Capellic has embarked on a journey to create our next-generation theme architecture to drive future website builds. We’ve dubbed that project Hyperdrive. Hyperdrive is a clean slate, a brand-new theme that is not built on top of any existing theme. Concepts from previous work will of course be carried forward into Hyperdrive, but we started with a clean directory and a fresh package.json file.
The philosophy
Hyperdrive is built on a philosophy of close-to-core, lightweight, and high-performance tooling that enables developers to work efficiently without their tools constantly fighting them. It is designed to encourage locality of behavior, observance of the rule of least power, and prioritize development and runtime speed over semantic purity and flashy (but expensive and often fruitless) features.
The stack
While the stack we’ve chosen to primarily pursue Capellic clients is perhaps “niche” in terms of Drupal development shops, it is in fact based on great success in other ecosystems. Our desired toolchain draws heavy inspiration from the work of folks in the Laravel and Rails communities, particularly the TALL stack and ‘The Majestic Monolith’ / Hotwire. Our toolchain is built to explicitly make it difficult to write poorly performing front-end code, or at the very least, decidedly unpleasant to do so.
Plain boring server-rendered Twig
The web world (and Drupal is no exception) has been awash in fervor for the technology darling of modern client-side rendered JavaScript frameworks. Developers have been shouting from the rooftops about how much more enjoyable it is for them to work with React and VueJS through modern tooling that instantaneously hot-reloads their components during development.
Having extensive VueJS experience myself, I fully understand the seductive qualities of the rapid iteration experience when developing in Vue. Likewise, the experience of using Vue components to build a UI is a pure joy. The syntax is so no-nonsense:
<div>
<some-cool-component
:some-property="`some-{$js-variable}-interpolated`"
@click="doJavaScriptThing"
>
<some-other-nested-component/>
</some-cool-component>
</div>
The same kind of thing looks utterly atrocious in Twig, I would choose this every time if it was the only consideration.
Likewise, the JavaScript APIs for Vue (especially the v2 options API) are really intuitive to work with, making it quite approachable to create really complex client-side behavior.
The Downsides of client-side JavaScript frameworks
Since I’ve spent several years raking in all the joys of developing with Vue, I’ve also had extensive experience with the downsides of this approach.
Performance
When you import JavaScript libraries, you are always paying a cost in network transport and browser CPU cycles. JavaScript must be delivered, parsed, and executed by the browser. All of this adds to the page loading time. It’s not a linear 1-to-1 tradeoff, but generally the more powerful and complex the JavaScript library, the bigger the performance penalty will be. This trade-off is absolutely worth it in some cases where sessions are long and devices are powerful, but for the “80%” use-case for our client websites, it absolutely is not. Read The Performance Inequality Gap (2024) for a deep dive into how performance problems disproportionately affect users on underpowered devices on slow connections and how JavaScript libraries make that worse.
Development and Maintenance Cost
The other elephant in the room is the massive increase in cost that a decoupled website brings to the table. When you start building decoupled experiences with tools like React and Vue, you are giving up years of work that has gone into developing the rendering, authentication, routing, and caching systems of Drupal core. Over the last few years, many community projects have sprung up to plug these gaps in the decoupled development experience, but in the end, even with these tools, the efficiency of developing a fully decoupled front-end versus a traditional server-rendered Drupal website will not even be close.
Our old friend YAGNI (You aren’t going to need it)
When it comes down to it at the end of the day, decoupled Drupal simply provides no worthwhile benefits for the vast majority of projects we work on. The tradeoffs are simply not worth it.
If your project is not looking to deliver varied content experiences to completely independent clients or to leverage highly interactive client-side experiences, it simply is not a good candidate for a (fully) decoupled architecture.
What we’re doing instead
Instead of chasing the hype of client-side frameworks, we’re doubling down on server-side rendered Twig, in fact, we’re looking into newer and more ergonomic ways to server render as much as we can, including dynamically generated and interactive parts of the page where possible.
Nothing is higher performance in the browser than pre-rendered HTML, and even client-side Javascript frameworks have realized this and integrated SSR capabilities.
CSS
The CSS Framework of choice for Hyperdrive is not particularly original in the web development world, but it is a bit of a shift for those working in the Drupal ecosystem. TailwindCSS has taken the web development world by storm, quickly migrating out from the Laravel community that Adam and Steve created it in to see wide adoption across the Rails community and others.
Tailwind is a radical departure in CSS methodology from what Capellic and many other Drupal shops have been doing for years. Drupal long ago adopted the SMACSS and BEM conventions as early adopters of the first wave of tools and techniques to stem the tide of out-of-control CSS bloat that was prevalent in web projects in the early 2010s, brought on by the glut of preprocessing tools that made it insanely easy to generate megabyte upon megabyte of duplicative bloated CSS, even when attempting to author incredibly clean source SASS and LESS files. By following style conventions like SMACSS and BEM, we gained a level of predictability and reliability that curbed some of the excess of CSS bloat and the conventions helped frontend developers move between projects with some promise of familiar conventions.
SMACSS and BEM however do not prevent massive amounts of duplication and highly specific selectors in CSS. Even with these conventions in place, well-meaning front-end developers can still lead themselves down the primrose path of highly specialized, deeply nested CSS selectors that are difficult to debug, difficult to change, highly duplicative, and expensive for browsers to calculate. Anybody who has spent time with Google’s PageSpeed Insights/Lighthouse will tell you this bloat is the cause of diagnostics such as “Reduced unused CSS”, “Largest Contentful Paint (render delay)”, “Eliminate render-blocking resources” among others.
TailwindCSS out of the box encourages these issues to never present themselves in the first place. TailwindCSS classes:
- Encourage an almost universal API that works across projects
- Completely avoid specificity wars (making debugging and changing styles incredibly easy)
- Enforce incredibly simple to compute selectors for the browser engine as runtime
- Fight rule duplication at its root, by skipping semantics and nesting altogether
The resulting stylesheets from most TailwindCSS projects are tiny compared to the equivalent hand-coded project stylesheet. Thanks to built-in rule purging, you don’t compile any utilities you don’t use, and every style that you include is represented by a single extremely simple selector.
Using Tailwind does present a few issues:
- We haven’t found a good way to split the output by component yet, so we are compiling a single monolithic stylesheet. This goes against the best practices I laid out in our performance blog series, but this issue is mitigated by the fact that the resulting stylesheet is so incredibly small that loading it on every page has thus far shown to not even cause Google Lighthouse to bat an eye at the file.
- Most experienced front-end developers immediately absolutely hate the idea of what Tailwind is. It is so against the way we’ve taught the majority of the industry to write CSS that it could all but start a mutiny on your team. As Adam Wathan says though, “It looks awful, and it works”. This is a complete paradigm shift in how people write CSS so it takes a lot of relearning for some.
Overall, the goal of adopting TailwindCSS is two-fold:
- Enable all developers to have a consistent experience across all projects. No special knowledge to contribute styles. This also has the benefit of enabling more full-stack developers to stretch their wings, helping to break down the artificial silo of frontend/backend splits. This ultimately improves our teams' flexibility and agility, allowing us to keep developers resourced across projects.
- Enable high-performance CSS as the default, and make it fairly difficult for our team to add complexity and poor performance patterns to our CSS.
JavaScript
In the world of high-performance web experiences, the best JavaScript is no JavaScript. The second best option is to use the least amount of JavaScript you can get away with to solve your problems.
In the modern browser, so many of the features that we used to rely on JavaScript for are now available within CSS itself. When possible, we should leverage those features, for things like animation support, runtime variables, etc.
When pure CSS isn’t enough, we will of course need to add JavaScript. The philosophy behind our JavaScript approach is very similar to how we want to write CSS:
- We want a standard way to do things that is easy to use and similar across projects.
- We want it to be high-performance by default (tiny filesizes, doing only what it needs to)
In many cases, this has meant writing “vanilla JavaScript” on projects, but more and more we’ve moved to using AlpineJS as the default way we handle JavaScript on client projects.
Alpine also comes from the Laravel community, and it can be thought of as a modern replacement for jQuery with a strong flavor of VueJS.
Alpine gives us some of the most useful features of modern heavyweight JavaScript frameworks like Vue and React but with a much, much smaller file size and runtime cost. Alpine provides features like two-way-databinding/statee management and easy-to-use directives for common tasks. These are features that React and Vue developers have grown very accustomed to and they make common JavaScript tasks much easier to manage. Implementing the ever-present mobile menu toggle that almost every website needs is incredibly simple in Alpine, the site header implementation in Hyperdrive is barely 40 lines of code but takes care of the structure, styling, accessibility, and interaction of the entire header component. All of the context to manage this component lives in a single file.
Given our team's experience level and comfort with Vue, Alpine’s very Vue-like syntax and patterns feel very natural. If and when we need to approach projects that require a more robust in-browser app experience, it will be a natural move to add Vue to projects that need it.
By default, the hope is to avoid installing any additional JavaScript libraries we don’t absolutely need for a given project. Alpine provides enough building blocks to handle the 80% use cases, and new dependencies will be heavily scrutinized, not just for their performance overhead but their maintenance burden as well.
At the time of writing, we are still evaluating some small additions to this core stack, like Alpine Ajax and HTMX to fill the last ‘gap’ in our default toolchain for handling dynamic server-side replacements in a simple and performant way.
Tooling
In terms of tooling, we also aren’t reinventing the wheel here, we’re simply picking some slick newer high-performance wheels from the latest generation of both the JavaScript build tool landscape and the Drupal community.
Vite as the bundler
When it came time to scaffold out the new theme, development speed was absolutely paramount. With this goal in mind, we chose to incorporate Vite as our bundler instead of the venerable Webpack. Because of this choice, compile times during development feel almost instantaneous, and compile times during CI runs and deploys are the lowest I think I’ve ever seen. At the time of writing, this is the summary of build times from our latest development site CI run:

It takes 15 seconds to build our theme, only 8 of which is the actual compile, the rest being setup and teardown tasks. For comparison, on an admittedly more complicated full production site for my main day-to-day client, running on Emusify, the theme alone takes nearly 5 full minutes to build. A lot of this time can be attributed to various things like the complexity of the theme itself, the number of components, the use of SASS instead of TailwindCSS, more vendor libraries to enable certain behaviors, and perhaps even tuning of the CI configuration itself, but that difference is stark. Hyperdrive can compile its theme more than 19 times before that website can compile itself once.
This means less money spent on CircleCI runtime, less carbon emitted every time we ship code, and unfortunately, less time for developers to sword-fight in the office.
Vite has been a game changer for productivity when working on Hyperdrive.
Single Directory Components
Hyperdrive is built with first-level support for Drupal’s emerging Single Directory Component architecture. We’ve long made use of the Components module in Drupal to leverage Emulsify’s component-driven architecture. With the release of Drupal’s Single Directory Components system, we’re able to drop that dependency and start building our themes and modules in a way that leverages all the potential benefits of adopting SDC, including future compatibility with Experience Builder, simplified libraries usage, component overrides, along with a set of additional features that are still materializing on the horizon.
The creation of Hyperdrive coincided with a desire to begin leveraging these new systems, so Hyperdrive has support from day one.
Storybook (for now)
Hyperdrive is still going to use Storybook to help speed the frontend development experience along. This was somewhat of a difficult choice to make as Storybook adds a lot of dependencies to our package.json, and maintaining ancillary tools was one of the major pain points we had been feeling with our theme stack to date. Dependencies add cost and risk to a project. Every additional package in our package.json increases our exposure to supply-chain attacks and increases the chance that something we rely on will be deprecated or lose support.
What we’ve managed to do instead is to conceptually treat Storybook in a slightly different way for Hyperdrive than we have for previous projects. Storybook will continue to be used for rapid development for our development team because the instant feedback of that experience was something we could not reliably replace with more Drupal-native tools at the time we started on the project. As a project matures and enters a maintenance phase, the existing Drupal ecosystem of development modules seemingly provides enough tooling through modules like SDC Styleguide and Vite to make maintaining the component library fairly painless going forward. We are interested in exploring modules like these to completely replace Storybook in our build pipeline as well but we’ve put that decision off for later.
Wrapping it up
We’re excited about where Hyperdrive is going to take our development. With the upcoming releases of tools like Experience Builder, the recent release of Single Directory Components, and the possibilities of things like HTMX in core, we think Hyperdrive is perfectly positioned to slot right into a high-performance Drupal future that we see on the horizon.