<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jonas Landgård</title>
    <description>The latest articles on DEV Community by Jonas Landgård (@jonas_landgrd_0ebeafe758).</description>
    <link>https://dev.to/jonas_landgrd_0ebeafe758</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3567640%2F972b1453-c372-455a-874a-5a35a2ea74db.jpg</url>
      <title>DEV Community: Jonas Landgård</title>
      <link>https://dev.to/jonas_landgrd_0ebeafe758</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jonas_landgrd_0ebeafe758"/>
    <language>en</language>
    <item>
      <title>Why We Built a Frontend That Outlives the Project</title>
      <dc:creator>Jonas Landgård</dc:creator>
      <pubDate>Fri, 17 Oct 2025 14:03:01 +0000</pubDate>
      <link>https://dev.to/jonas_landgrd_0ebeafe758/why-we-built-a-frontend-that-outlives-the-project-4ehp</link>
      <guid>https://dev.to/jonas_landgrd_0ebeafe758/why-we-built-a-frontend-that-outlives-the-project-4ehp</guid>
      <description>&lt;p&gt;Every new client e-commerce project felt like déjà vu. Different brands, different designs — yet the same foundational code had to be rebuilt from scratch: cart logic, product lists, authentication, checkout state. Each project started with weeks of boilerplate before any real design work began. As our client base grew, so did the maintenance burden. Fixing a bug in one storefront meant repeating the same patch across several others. Over time, our frontend code became the bottleneck, not the enabler, of our delivery speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The agency paradox
&lt;/h2&gt;

&lt;p&gt;As an agency working with many e‑commerce clients, we needed both speed &lt;strong&gt;and&lt;/strong&gt; freedom. Each customer deserved a unique brand experience, but under the hood, 70–80 % of the logic was identical. We wanted to reuse that shared logic without locking designers or developers into a specific look, stack, or framework. Traditional theme systems and component libraries couldn’t give us that balance. They saved time early but made innovation harder later.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Our first attempt (and why it didn’t work)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Our first attempt was a shared component library. It seemed reasonable: one source of truth for all UI parts. But as the library grew, it became harder to maintain. The &lt;strong&gt;QuantityButtons&lt;/strong&gt; component from our old ecom-components repo perfectly captured the pain. It used more than 30 CSS variables, had a fixed DOM structure, and styling leaked between nested components. Overriding colors or layouts required deep selector hacks and &lt;strong&gt;!important&lt;/strong&gt; rules. We realised reuse at the component level wasn’t flexible enough. We needed reuse at the logic level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt; Logic and presentation were tangled, which slowed every new project and created unnecessary technical debt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before: monolithic component
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Before: monolithic UI component
// – tightly coupled markup, styling and behavior
// – theming required CSS overrides and !important hacks
// – every project had to rebuild or restyle this component
export function QuantityButtons({ className, theme, ...props }) {
  return (
    &amp;lt;div className={`qty ${theme} ${className}`}&amp;gt;
      &amp;lt;button className="decrement"&amp;gt;–&amp;lt;/button&amp;gt;
      &amp;lt;input className="input" type="number" {...props} /&amp;gt;
      &amp;lt;button className="increment"&amp;gt;+&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
// Over 30 CSS variables and nested selectors to adapt styling per project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach caused endless style collisions and made it nearly impossible to isolate design systems between clients.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Our headless frontend pattern&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The breakthrough was treating the frontend the same way headless commerce treats the backend — by separating logic from presentation. Each domain became its own headless module: hooks like &lt;strong&gt;useCart&lt;/strong&gt;, &lt;strong&gt;useProductList&lt;/strong&gt;, or &lt;strong&gt;useQuantityButtons&lt;/strong&gt; handle state, validation, and actions, but make no assumptions about how the UI looks. Developers can compose their own markup using these hooks or use our prebuilt composition patterns. This means our logic layer behaves like an API: stable, documented, versioned, and UI‑agnostic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this means:&lt;/strong&gt; The logic is universal; the experience is unique. Each project can render the same data and state logic in completely different ways.&lt;/p&gt;

&lt;h3&gt;
  
  
  After: headless logic + composition
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const QuantityButtons = {
  Root: ({ value, onValueChange, children, ...props }) =&amp;gt; {
    const hookValues = useQuantityButtons({ value, onValueChange })
    return (
      &amp;lt;QuantityButtonsContext.Provider value={hookValues}&amp;gt;
        &amp;lt;div {...props}&amp;gt;{children}&amp;lt;/div&amp;gt;
      &amp;lt;/QuantityButtonsContext.Provider&amp;gt;
    )
  },
  Decrement: (props) =&amp;gt; {
    const { getDecrementProps } = useQuantityButtonsContext()
    return &amp;lt;button {...getDecrementProps(props)} /&amp;gt;
  },
  Increment: (props) =&amp;gt; {
    const { getIncrementProps } = useQuantityButtonsContext()
    return &amp;lt;button {...getIncrementProps(props)} /&amp;gt;
  },
  Input: (props) =&amp;gt; {
    const { getInputProps } = useQuantityButtonsContext()
    return &amp;lt;input {...getInputProps(props)} /&amp;gt;
  },
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Logic and UI are now fully decoupled. The hook layer provides the logic, while the composition pattern allows each client project to define its own markup and styling.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Usage in a client project&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;QuantityButtons.Root
  value={quantity}
  onValueChange={handleQuantityChange}
  className="product-quantity"
&amp;gt;
  &amp;lt;QuantityButtons.Decrement className="decrement-btn" /&amp;gt;
  &amp;lt;QuantityButtons.Input unit="st" className="quantity-field" /&amp;gt;
  &amp;lt;QuantityButtons.Increment className="increment-btn" /&amp;gt;
&amp;lt;/QuantityButtons.Root&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how client teams can implement their own brand‑specific UI without touching the shared logic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxw6gkox1o7te05qyt96v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxw6gkox1o7te05qyt96v.png" alt="Same logic, different UI. Three ways of rendering the quantityButton component." width="800" height="80"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How we structured it&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We structure our storefront as a set of modular, versioned npm packages that can be mixed and matched depending on project needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The monorepo layout&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;haus-storefront-components/
  ├─ core/                 # shared types, config, GraphQL plumbing
  ├─ cart/                 # cart hooks, reducers, mutations
  ├─ search/               # product search and filters
  ├─ auth/                 # login, registration, account state
  └─ ...                   # other business domains
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each package owns its own version and changelog. They are published independently, which lets us upgrade only the modules that matter to a given project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependency flow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Core&lt;/strong&gt; provides base utilities and shared contracts.&lt;/li&gt;
&lt;li&gt; Each domain package imports core and exposes typed hooks (useCart, useSearch, etc.).&lt;/li&gt;
&lt;li&gt; Client projects or integrations import these packages as peer dependencies.&lt;/li&gt;
&lt;li&gt; The monorepo is managed with Changesets to ensure semantic versioning and safe publishing.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Client repositories
&lt;/h3&gt;

&lt;p&gt;Each client repository contains only what’s needed for its own storefront:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  a &lt;strong&gt;component manifest&lt;/strong&gt; describing which shared packages and hooks are in use,&lt;/li&gt;
&lt;li&gt;  a &lt;strong&gt;UI layer&lt;/strong&gt; implementing brand‑specific markup and styling built on top of those hooks,&lt;/li&gt;
&lt;li&gt;  any &lt;strong&gt;brand assets&lt;/strong&gt;, translations, and deployment configuration.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// package.json (client repo)
{
  "dependencies": {
    "@haus/cart": "^2.4.1",
    "@haus/catalog": "^1.9.0"
  }
}
// → patch @haus/cart to 2.4.2 without touching @haus/catalog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we push an update to, say, &lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/haus"&gt;@haus&lt;/a&gt;/&lt;a href="mailto:cart@2.4.1"&gt;cart@2.4.1&lt;/a&gt;&lt;/strong&gt;, the client repo automatically receives it via dependency update. Fixes and new features propagate predictably without rewriting UI code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integration layer
&lt;/h3&gt;

&lt;p&gt;We maintain lightweight integration bridges that adapt the same headless packages to different environments —anything fromcustom React apps to WordPress/Elementor widgets. The core logic is identical; only the rendering and data‑wiring differ. This makes it possible to reuse the same commerce logic across multiple platforms, from marketing sites to mobile apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;For an agency, speed isn’t just about code — it’s about margin, delivery risk, and client trust.&lt;/p&gt;

&lt;p&gt;The results were immediate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Onboarding time&lt;/strong&gt; for new projects dropped from several weeks to a day or so.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Consistency&lt;/strong&gt; improved; a fix or new feature in one module benefits every client.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Design freedom&lt;/strong&gt; increased, since the UI layer is fully independent of the logic layer.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Maintenance&lt;/strong&gt; became predictable and version‑controlled. We can patch &lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/core"&gt;@core&lt;/a&gt;/cart&lt;/strong&gt; without touching product lists or search.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When we tested the architecture, we built a complete demo storefront from scratch: product pages, category listings, cart, and login.&lt;/p&gt;

&lt;p&gt;It took one developer &lt;strong&gt;two days&lt;/strong&gt;, and almost all that time went into styling. The logic was already in place. Once styled, that setup becomes a boilerplate. Spinning up a new project now takes &lt;strong&gt;minutes&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters for agencies
&lt;/h2&gt;

&lt;p&gt;For agencies, the difference is profound. You no longer rebuild the same e‑commerce logic under new branding each time. Modular, versioned packages turn the frontend into a maintainable product that evolves independently from any single client. It’s a mindset shift: from &lt;strong&gt;project delivery&lt;/strong&gt; to &lt;strong&gt;platform engineering&lt;/strong&gt;. Agencies can deliver faster, support multiple clients more efficiently, and still craft unique brand experiences.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Discussions about composable frontend architecture are becoming more common, from practical guides on &lt;a href="https://www.freecodecamp.org/news/complete-micro-frontends-guide/" rel="noopener noreferrer"&gt;freeCodeCamp&lt;/a&gt;, to platform-oriented insights from &lt;a href="https://mia-platform.eu/blog/composable-frontend/" rel="noopener noreferrer"&gt;Mia-Platform&lt;/a&gt;, and developer perspectives on &lt;a href="https://directus.io/blog/solving-the-multiple-frontend-problem-in-modern-applications" rel="noopener noreferrer"&gt;Directus&lt;/a&gt; and &lt;a href="https://dev.to/joacod/microfrontends-a-developers-guide-best-practices-and-lessons-learned-1nlp"&gt;DEV&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These articles explore modularisation, micro-frontends, and shared dependencies. Most focus on single-product contexts.&lt;/p&gt;

&lt;p&gt;Our challenge was different: as an agency, we needed a &lt;strong&gt;composable frontend architecture that works across many client projects&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of treating the frontend as a single modular app, we treat it as a &lt;strong&gt;set of versioned packages&lt;/strong&gt; that can power ten different storefronts, each with its own design system and deployment pipeline.&lt;/p&gt;

&lt;p&gt;That shift, from one modular app to a shared logic layer powering many independent storefronts, is what makes our approach unique.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Looking ahead
&lt;/h2&gt;

&lt;p&gt;The idea of a frontend that outlives any single project started as a technical refactor, but it ended up changing how we think about building digital products altogether. Instead of disposable codebases, we now have a living system that powers multiple experiences across platforms.&lt;/p&gt;

&lt;p&gt;The next logical step is to let AI build on top of this foundation — not to invent logic, but to compose new experiences. With every module versioned and proven in production, an AI could safely generate complete storefronts in near real time by assembling layout, flow, and design from battle-tested logic.&lt;/p&gt;

&lt;p&gt;The outcome: faster launches, tighter feedback loops, and more space for human creativity.&lt;/p&gt;

&lt;p&gt;Because this approach doesn’t just change how we code — it changes how we &lt;em&gt;build&lt;/em&gt;, scope, deliver, and evolve digital products. And it lets our developers focus on what truly matters: creating new value, not rebuilding the old.&lt;/p&gt;

&lt;p&gt;If you want to see more implementation details, we’re planning a follow‑up deep dive on dependency management, type safety, and testing strategies for modular frontends. Feedback and questions are welcome. Drop a comment or &lt;a href="https://www.linkedin.com/in/jonas-landgard" rel="noopener noreferrer"&gt;connect with me on LinkedIn&lt;/a&gt; to continue the discussion.&lt;/p&gt;

</description>
      <category>react</category>
      <category>frontend</category>
      <category>ecommerce</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
