DEV Community

Cover image for Components for Developers: Why I Joined Novu
Sokratis Vidros for novu

Posted on • Originally published at novu.co

Components for Developers: Why I Joined Novu

Today, I pen this post with excitement, and a forward-looking spirit. For the past four years, my team was building components that are worth a thousand APIs! We aimed to offer a Google-level authentication experience with amazing frontend DX in a few lines of code.

We invested our time in building domain-specific features and packaging them in beautiful, ready-to-use components that describe sophisticated, stateful, multi-step flows such as <SignIn/> and <SignUp/>. Such components are built on four layers:

  1. Frontend API
  2. Vanilla JS SDK on the window object
  3. Framework-specific, headless SDK (i.e. hooks for React)
  4. UI Components

The following sections contain insights and tips for component builders so far.

Layering

Offer four polished layers to your customers. Most developers will start using the UI. As their product evolves, they will gradually opt-in for control using more layers.

DX is key. Perfect it at every stage. Make the API intuitive and frictionless and offer wow moments here and there. I strongly recommend reading the Developer Experience book by Addy Osmani.

Build each layer on top of each other. For example, @acme/sdk -> @acme/js -> @acme/react/hooks -> @acme/react/ui. Do not rely on internal libraries to which customers have no access. Empower the community to contribute.

If the use case allows it, host your components for your customers. For example, Stripe Checkout, Clerk or Clerk Account Portal. This is the fastest way for your customers to get started.

State management

In the front-end realm, I find it helpful to separate states into macro and micro states. Macro state affects the global state of the browser tab, including routing, layout, flow state, and data management. Micro state is about micro UI interactions, such as toggling a collapsible or a mouseenter event on a popover.

Keep as much macro state in the frontend API on the server side and the address bar. Use JWTs to define context, such as the current user or organization. This will ease building SDKs across devices. Process macro state on the client side via a framework agnostic, preferably event-based state manager outside the UI Components layer. Then, use native framework bindings such as useSyncExternalStore in React. Micro state should be as simple as useState.

Customization

Make your components beautiful, accessible, and responsive. Please don’t sell your company’s branding through them. Offer white-labeled, polished look-and-feel that is fully customizable via code.

Currently, component theming is mostly done with predefined classes that developers can override (i.e. .acme-card) combined with data attribute selectors, inline style props, or CSS custom properties. For optimal DX, let developers use their own CSS framework. Allow them to pass their classes from their favorite framework such as Tailwind or Panda.

Since components are embedded in the host application avoiding redirects, styling should leak. The most common unnecessary customization options component authors offer are the font family, color, or aliasing. Inherit them from the parent, host application container.

At the time, we relied on a popular CSS-in-JS solution that injected a element at the top of the document and juggled with CSS Specificity. With the recent trends in the ecosystem around headless and server components, the expectations are shifting. Moreover, there is new, stable, exciting tech such as better zero runtime CSS-in-JS system, Cascade layers, Scoped CSS, and more. Use the platform to the maximum. And let developers localize via prop injection.

Routing and bookmarkability

Components describe flows. Flows can be mapped to URL paths. Components must function inside the routing systems of modern web frameworks such as Next.js or Remix or without it. Framework routers leverage the window.history API. Build a powerful, nested router that works in tandem with the main application router, and supports three different strategies, depending on the architecture of the host application:

  1. Hash
  2. Path
  3. Memory

Don’t forget to preserve the hash and search parameters set by the host applications.

Packaging

Packaging can be done in many ways. Each layer can be packaged separately or combined into a single big package with subpath exports. Hooks can be a separate package from components for example @acme/react-hooks and @acme/react-ui. Moreover, you can ship one package per framework @acme/react or meta-framework @acme/nextjs. Or if you feel bold enough with tree shaking and lazy loading, go for one package to rule them all and export all layers from there.

More packages offer more flexibility but create a complex dependency graph that makes versioning and code management harder. Pick the strategy that suits your product and tame source code management with modern mono repos using Turbo or Nx. Moreover, set a budget for your chunk sizes.

Distribution

NPM packages are published by authors and consumed in the package.json of the host application. New releases and patches follow the same process, reducing the control of the release for the publisher.

Hot loading via CDN can take you really far on the client side. It ensures that hotfixes and new features are delivered immediately to your customers. You can also instrument a gradual rollout process or AB test. Cloudflare workers or similar tech are great choices.

You have to be more creative in the RSC realm. Try to weave streams from different RSC servers.

What’s next?

UI Example showing output of REACT code.
UI generated with v0.dev

This month, I joined the first open-source notification infrastructure. I am excited about diving into a new component journey around real-time notifications (and more). I am also thrilled to see more and more companies hopping on the wagon. We’ve rolled up our sleeves to make your next application header be:

<Header>
  <Logo />
  <NotificationCenter />
  <UserButton />
</ Header>
Enter fullscreen mode Exit fullscreen mode

Take care, see you in GitHub, and we're hiring!

Top comments (2)

Collapse
 
jgdevelopments profile image
Julian Gaston

This is pretty cool stuff, i'm interested to find out more.

You obviously, put a lot of time into this article. Thank you for your efforts! Being thorough is rather "socratic" haha. Seriously, this was an interesting read and it left me curious to learn more about Novu. Thanks for the share and I look forward to checking this out. If Novu survives and thrives I think it could be pretty big, if performant.

The simplicity of this <Header> sample is both scary and enticing.

Collapse
 
sokratisvidros profile image
Sokratis Vidros

Thanks, Julian. Fascinating times to build such an offering. We are rolling up our sleeves to make it happen!