Duct UI

Duct vs Other Frameworks

How Duct compares to React, Vue, and Svelte

Understanding how Duct differs from popular frameworks helps you make informed architectural decisions. Here’s an honest comparison of approaches and trade-offs.

Component Library Comparison

This section compares Duct as a component library for building interactive UI components.

FeatureDuctReactVueSvelteWeb Components
ArchitectureBlueprint pattern with separationComponent with mixed concernsSFC with sectionsCompiled componentsCustom elements with class
Virtual DOM❌ Noβœ… Yesβœ… Yes❌ No❌ No
State ManagementExternal/ManualuseState/useReducerReactive dataReactive storesManual/Properties
Learning CurveLowMedium-HighMediumLow-MediumMedium
Template SyntaxJSXJSXTemplate/JSXEnhanced HTMLHTML/Template literals
Logic LocationSeparate bind functionMixed in componentScript sectionScript sectionClass methods
ReactivityEvent-drivenRe-render basedProxy-basedCompile-timeManual/Observers

Detailed Comparisons

Notice
Pay attention to how Duct code is direct, explicit, and easily understandable. What you see is what executes - no hidden abstractions, magic behaviors, or complex state management. This makes debugging straightforward and code behavior predictable.

Duct vs React

React popularized component-based UI development, but Duct takes a fundamentally different approach to organizing component logic.

React Component

function Button({ label, onClick }) {
  const [clicked, setClicked] = useState(false);

  return (
    <button
      className={clicked ? 'btn-clicked' : 'btn'}
      onClick={() => {
        setClicked(true);
        onClick();
      }}
    >
      {label}
    </button>
  );
}

Duct Component

// Render function
function render(props) {
  return (
    <button class="btn">
      {props.label}
    </button>
  );
}

// Bind function
function bind(el, eventEmitter) {
  let clicked = false;

  const handleClick = () => {
    clicked = true;
    el.className = 'btn-clicked';
    eventEmitter.emit('click');
  };

  el.addEventListener('click', handleClick);

  return {
    release: () => {
      el.removeEventListener('click', handleClick)
    }
  };
}

// Create and export component
const Button = createBlueprint(
  { id: "my/button" },
  render,
  { bind }
);

export default Button;

Duct Advantages

  • βœ“ No re-rendering overhead
  • βœ“ Direct DOM control
  • βœ“ Clear separation of concerns
  • βœ“ Explicit behavior
  • βœ“ No hooks complexity

React Advantages

  • β€’ Large ecosystem
  • β€’ Extensive tooling
  • β€’ Huge community
  • β€’ More job opportunities
  • β€’ Mature libraries

Duct vs Vue

Vue offers a middle ground between React and traditional templating, while Duct goes further in separating concerns.

Vue Component

<template>
  <button
    :class="buttonClass"
    @click="handleClick"
  >
    {{ label }}
  </button>
</template>

<script>
export default {
  props: ['label'],
  data() {
    return { clicked: false };
  },
  computed: {
    buttonClass() {
      return this.clicked ? 'btn-clicked' : 'btn';
    }
  },
  methods: {
    handleClick() {
      this.clicked = true;
      this.$emit('click');
    }
  }
};
</script>

Duct Component

// Render function
function render(props) {
  return (
    <button class="btn">
      {props.label}
    </button>
  );
}

// Bind function
function bind(el, eventEmitter) {
  let clicked = false;

  const handleClick = () => {
    clicked = true;
    el.className = 'btn-clicked';
    eventEmitter.emit('click');
  };

  el.addEventListener('click', handleClick);

  return {
    release: () => {
      el.removeEventListener('click', handleClick)
    }
  };
}

// Create and export component
const Button = createBlueprint(
  { id: "my/button" },
  render,
  { bind }
);

export default Button;

Duct Advantages

  • βœ“ No reactivity magic to debug
  • βœ“ Explicit DOM control
  • βœ“ Clear separation of template/logic
  • βœ“ Standard JavaScript patterns
  • βœ“ Predictable performance

Vue Advantages

  • β€’ Gentle learning curve
  • β€’ Progressive enhancement
  • β€’ Excellent documentation
  • β€’ Built-in state management
  • β€’ Single-file components

Duct vs Svelte

Both Duct and Svelte avoid virtual DOM, but they differ significantly in their compilation and component organization approaches.

Svelte Component

<script>
  export let label;
  let clicked = false;

  function handleClick() {
    clicked = true;
    dispatch('click');
  }
</script>

<button
  class={clicked ? 'btn-clicked' : 'btn'}
  on:click={handleClick}
>
  {label}
</button>

Duct Component

// Render function
function render(props) {
  return (
    <button class="btn">
      {props.label}
    </button>
  );
}

// Bind function
function bind(el, eventEmitter) {
  let clicked = false;

  const handleClick = () => {
    clicked = true;
    el.className = 'btn-clicked';
    eventEmitter.emit('click');
  };

  el.addEventListener('click', handleClick);

  return {
    release: () => {
      el.removeEventListener('click', handleClick)
    }
  };
}

// Create and export component
const Button = createBlueprint(
  { id: "my/button" },
  render,
  { bind }
);

export default Button;

Duct Advantages

  • βœ“ No compile step required
  • βœ“ Runtime transparency
  • βœ“ Standard build tools
  • βœ“ Explicit behavior
  • βœ“ Easy debugging

Svelte Advantages

  • β€’ Tiny bundle sizes
  • β€’ Compile-time optimizations
  • β€’ Built-in animations
  • β€’ Simple reactive syntax
  • β€’ No runtime overhead

Duct vs Web Components

Both Duct and Web Components embrace direct DOM manipulation and standards-based approaches, but they differ in their component organization and abstraction levels.

Web Components

class ButtonElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    this.shadowRoot.innerHTML = `
      <style>
        button { padding: 8px 16px; }
        .btn { /* base button styles */ }
        .btn-clicked { /* clicked button styles */ }
      </style>
      <button class="btn">${this.getAttribute('label')}</button>
    `;

    this.button = this.shadowRoot.querySelector('button');
    this.button.addEventListener('click', this.handleClick.bind(this));
  }

  handleClick() {
    this.button.className = 'btn-clicked';
    this.dispatchEvent(new CustomEvent('button-click'));
  }

  disconnectedCallback() {
    this.button?.removeEventListener('click', this.handleClick);
  }
}

customElements.define('my-button', ButtonElement);

Duct Component

// Render function
function render(props) {
  return (
    <button class="btn">
      {props.label}
    </button>
  );
}

// Bind function
function bind(el, eventEmitter) {
  let clicked = false;

  const handleClick = () => {
    clicked = true;
    el.className = 'btn-clicked';
    eventEmitter.emit('click');
  };

  el.addEventListener('click', handleClick);

  return {
    release: () => {
      el.removeEventListener('click', handleClick)
    }
  };
}

// Create and export component
const Button = createBlueprint(
  { id: "my/button" },
  render,
  { bind }
);

export default Button;

Duct Advantages

  • βœ“ Clear separation of render/logic concerns
  • βœ“ No Shadow DOM complexity
  • βœ“ Simpler component composition
  • βœ“ Framework-agnostic JSX templates
  • βœ“ Explicit lifecycle management
  • βœ“ Better TypeScript integration

Web Components Advantages

  • β€’ Native browser standard
  • β€’ True encapsulation with Shadow DOM
  • β€’ Framework interoperability
  • β€’ No build step required
  • β€’ CSS isolation by default
  • β€’ Custom element lifecycle

When to Choose Component Libraries

Choose Duct Components When

  • βœ“ You value explicit, debuggable code
  • βœ“ You need direct DOM control
  • βœ“ You want minimal abstractions
  • βœ“ You're building long-lived applications
  • βœ“ You work with AI code generation
  • βœ“ You prefer separation of concerns
  • βœ“ You need predictable performance
  • βœ“ You want clear debugging paths
  • βœ“ You prioritize maintainability

Choose React When

  • β€’ You need the largest ecosystem
  • β€’ You have React expertise
  • β€’ You need enterprise support
  • β€’ You're building complex SPAs
  • β€’ You need extensive third-party libraries

Choose Vue When

  • β€’ You want balanced complexity
  • β€’ You like single-file components
  • β€’ You need official router/state management
  • β€’ You're migrating from jQuery
  • β€’ You want good documentation

Choose Svelte When

  • β€’ You want the smallest bundle sizes
  • β€’ You like compile-time optimizations
  • β€’ You want built-in animations
  • β€’ You prefer simpler mental models
  • β€’ You're building content sites

Choose Web Components When

  • β€’ You need true framework interoperability
  • β€’ You want standards-based components
  • β€’ You need CSS encapsulation
  • β€’ You're building design systems
  • β€’ You want no build dependencies

Complete Framework Comparison

This section compares Duct as a complete framework for building full applications with SSG, routing, and content management.

FeatureDuctNext.jsNuxtSvelteKitAstro
SSG/SSRβœ… Built-in SSGβœ… Full SSG/SSRβœ… Full SSG/SSRβœ… Full SSG/SSRβœ… Content-focused SSG
File-based Routingβœ… Built-inβœ… Pages & app routerβœ… Built-inβœ… Built-inβœ… Built-in
Content Managementβœ… Markdown + assets⚠️ Via plugins⚠️ Via modules⚠️ Manual setupβœ… Content collections
Asset Managementβœ… Automatic copyingβœ… Advanced optimizationβœ… Built-in optimization⚠️ Via adaptersβœ… Built-in optimization
Learning CurveLowHighMedium-HighMediumLow-Medium
ArchitectureSSG with hydrated islandsFull-stack ReactFull-stack VueFull-stack SvelteIslands with framework choice
API Routes❌ Static-onlyβœ… Full API supportβœ… Full API supportβœ… Full API support⚠️ Via integrations
Template SystemNunjucks (static) + JSX (components)JSX everywhereVue templates everywhereSvelte templates everywhereAstro templates + framework islands
Static/Dynamic Separationβœ… Clear separation⚠️ Mixed concerns⚠️ Mixed concerns⚠️ Mixed concernsβœ… Islands pattern
Deployment TargetStatic hosting (CDN)Vercel/Node.js/EdgeVarious platformsVarious adaptersStatic hosting/Edge

When to Choose Complete Frameworks

Choose Duct Framework When

  • βœ“ You're building content-driven sites
  • βœ“ You want simple SSG without complexity
  • βœ“ You need automatic content management
  • βœ“ You prefer static-first architecture
  • βœ“ You want clear static/dynamic separation
  • βœ“ You like proven templating (Nunjucks)
  • βœ“ You want fast, SEO-friendly sites
  • βœ“ You prefer explicit over magic
  • βœ“ You value simplicity and maintainability

Choose Next.js When

  • β€’ You need full-stack React applications
  • β€’ You want advanced SSR/SSG features
  • β€’ You need API routes and serverless
  • β€’ You're building e-commerce sites
  • β€’ You need enterprise React features

Choose Nuxt When

  • β€’ You prefer Vue ecosystem
  • β€’ You want opinionated structure
  • β€’ You need full-stack capabilities
  • β€’ You want automatic routing
  • β€’ You need Vue-specific optimizations

Choose SvelteKit When

  • β€’ You love Svelte's approach
  • β€’ You want small bundle sizes
  • β€’ You need full-stack capabilities
  • β€’ You prefer compile-time optimizations
  • β€’ You want simple mental models

Choose Astro When

  • β€’ You want zero JS by default
  • β€’ You need content-heavy sites
  • β€’ You want islands architecture
  • β€’ You need framework flexibility
  • β€’ You prioritize maximum performance
Remember: Each framework solves different problems well. Choose based on your project needs, team expertise, and long-term maintenance goals rather than popularity alone.