In our last installment, Cycle's New Interface part 1 , we covered the myriad of new UI changes added to Cycle's portal. In this part, we walk through five of the tough engineering choices made when developing the new interface, discussing the alternatives that were considered, and shining a light on some of the technology our engineering team utilizes today.
Part 2: The Engineering Behind Cycle's New Portal
Whenever someone says that you need to rebuild a complex piece of software from scratch, it's almost universally the wrong decision. Almost, however, doesn't mean always. In the case of Cycle's “old portal”, there was half a decade of technical debt, built on top of libraries that were no longer maintained or updated, with bolted-on modules built to support new features of Cycle that were never planned. While normally, you'd take some time and upgrade/rebuild things piece by piece, it was going to be far better for the future of our portal if we started over.
This is mostly because, when the original was built, the frontend/web ecosystem was in disarray and a new framework was coming out seemingly every week. While we got it right with React, even the standards in the React ecosystem were in flux. Class based components, functional components, Redux, MobX, CSS-in-JS, Tailwind… the number of decisions you had to make and be right on were overwhelming.
Today, the shape-shifting has slowed down, and there are stable and mature winners in the frontend world. While decisions still have to be made, it's much less worrisome that the choices we make today will be obsolete in the next 6-12 months. And now that Cycle itself is more mature, with the most fundamental and complex pieces stable, it's far easier to engineer an interface that will be more capable of growing with it for the next 5+ years.
So, with that preamble out of the way, let's walk through the same choices our engineering team has made over the last 10 months to bring to you our new and powerful portal. The options for each choice aren't exhaustive, but are the main ones we looked at after some basic elimination.
Choice 1 - Bundling Tool
- Webpack
- Vite
- Esbuild
- Parcel
Every web app needs a good bundling tool. Traditionally the de facto choice was Webpack, but over the last few years of using it, we've found it bulky and awkward. Not to mention, it tends to be slow when compared to more modern tools.
After some testing, we decided to go with Vite. It is modern and follows modern web principles, is extremely fast at cold-boot builds and hot reloading, and has a ton of active development. The configuration is dead simple, which cannot be said for Webpack. Looking ahead, there are plugins for building PWAs (which we'll discuss in the final entry of this series), and plugins for simplifying sourcemap uploads to Sentry for event monitoring.
All of these features put Vite at the top of our list, and has made development on the new portal seamless.
Choice 2 - React or Not React?
- React
- Not React (Vue, HTMX, Svelte, etc)
This was by far the easiest choice we made. There are a lot of opponents, die-hards, and fanatics in the React ecosystem. However, React comes with a massive ecosystem of component libraries, community support, and is used by a ton of major web applications. Not to mention, our team has tons of experience with it, and hiring more frontend developers in the future will be easy, since React is the de facto library everyone learns.
While we most certainly could have used another library instead, development would have been slower, with far less benefit for us. Therefore, React was the best choice.
Choice 3 - State Management
- Redux
- MobX
- Remix
- Redux Toolkit
- RxJS
This choice was quite a bit more difficult to make. As anyone who's worked on even a slightly complex web app will tell you, state management is one of the most challenging things to get right. The old portal was built on top of vanilla Redux, with a LOT of customization around it. It had become a giant tangled mess, as we added on websocket notifications, complex async interactions, “sagas”, and a whole lot more over the years.
While personally I'm a big fan of RxJS, it can be difficult to get started and is quite a paradigm shift if you've never used it before. For us, having prior experience with Redux meant a solution based on that would be ideal — we're big fans of traceability and the abstraction Redux brings.
One of the other important factors in our decision making was our API design. Cycle's API is REST-based, but has some live socket pieces to it as well. Our state management system would need to gracefully handle local logic, API/asynchronous events, and streaming websockets. We had made the decision to codify our API into the OpenAPI spec. If we could find something that would tie into that for auto-generated API calls, it would tip the scales.
It just so happened that the perfect intersection of all these criteria was Redux Toolkit.
Redux Toolkit reduces (and in some cases eliminates) boilerplate. There's an OpenAPI codegen that turns all of our API calls into React hooks automatically, while keeping everything type-safe. If the spec is updated, we immediately know what needs to change.
What is really impressive about Redux Toolkit is how it handles queries. You can “tag” data — for example: { type: "Container", id: "abc123" }
— and invalidate it when an event occurs (like a websocket message), which automatically refetches it in the background.
React components are subscribed to these queries, so when the cache updates, the component re-renders. Redux Toolkit keeps track of active subscriptions and avoids unnecessary updates.
An honorary mention goes to Remix, which we use for everything except the portal (signup, status page, etc). It's better suited for traditional websites than highly dynamic apps with streaming data.
An example of subscriptions and tabs in the portal
The New Data Flow
With that context, here's how the modern portal works:
- The relevant components are rendered for the current page and subscribe to the data they care about.
- The cache is empty, so the component makes an API call.
- The returned data is tagged appropriately.
- A form is submitted or a websocket notification indicates a data change.
- The cache is now stale, so Redux Toolkit fetches fresh data in the background.
- Data updates and the component re-renders.
- We navigate away, unsubscribing from the data.
- After one minute, the cache is cleared. Re-navigation triggers a refetch.
This is a simplified but accurate description of the current process. There are edge cases and more advanced logic, but the portal functions primarily on these principles.
Choice 4 - UI and Component Library
- Material UI
- Ant
- Semantic UI
- Chakra
- Other Prebuilt UI
- In-House
- Tailwind + Headless UI
Once we made the decision to stick with React, the question became which UI/component library to use. While redesigning the portal, we wanted to keep things familiar yet improved.
We chose Tailwind for flexibility and Headless UI for unstyled logic, and built our own component system in-house. This allowed us to reuse UI across multiple services and apps.
Sharing Components Between Apps with Monorepos
To achieve reusability, we created a monorepo with Yarn and TurboRepo. This setup allowed us to componentize our UI, share Tailwind configs, and version everything together at the git level. We also included a shared utilities library for working with Cycle's data structures.
Choice 5 - Testing
- Jest
- Playwright
- Mocha
- Cypress
- React Testing Library
- Enzyme
- Vitest
To round off our choices, we had to decide on a testing strategy.
The winners: Vitest & Playwright .
Since we use Vite already, Vitest fits perfectly for unit testing. It supports Rust-like in-source tests and is easy to use.
For integration and end-to-end testing, we chose Playwright. It enables headless browser testing, mocking API requests, and capturing regressions in full workflows. It's been vital for testing edge cases with confidence.
Tying it All Together
These were far from all the decisions we made while building the new portal, but they were some of the hardest and most impactful. With this stack, we delivered a stable, fast, and future-proof interface for Cycle in under 10 months. In the final part of this series, we'll look at what's coming next for the portal and what users can expect to see soon.