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.
Feature | Duct | React | Vue | Svelte | Web Components |
---|---|---|---|---|---|
Architecture | Blueprint pattern with separation | Component with mixed concerns | SFC with sections | Compiled components | Custom elements with class |
Virtual DOM | β No | β Yes | β Yes | β No | β No |
State Management | External/Manual | useState/useReducer | Reactive data | Reactive stores | Manual/Properties |
Learning Curve | Low | Medium-High | Medium | Low-Medium | Medium |
Template Syntax | JSX | JSX | Template/JSX | Enhanced HTML | HTML/Template literals |
Logic Location | Separate bind function | Mixed in component | Script section | Script section | Class methods |
Reactivity | Event-driven | Re-render based | Proxy-based | Compile-time | Manual/Observers |
Detailed Comparisons
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.
Feature | Duct | Next.js | Nuxt | SvelteKit | Astro |
---|---|---|---|---|---|
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 Curve | Low | High | Medium-High | Medium | Low-Medium |
Architecture | SSG with hydrated islands | Full-stack React | Full-stack Vue | Full-stack Svelte | Islands with framework choice |
API Routes | β Static-only | β Full API support | β Full API support | β Full API support | β οΈ Via integrations |
Template System | Nunjucks (static) + JSX (components) | JSX everywhere | Vue templates everywhere | Svelte templates everywhere | Astro templates + framework islands |
Static/Dynamic Separation | β Clear separation | β οΈ Mixed concerns | β οΈ Mixed concerns | β οΈ Mixed concerns | β Islands pattern |
Deployment Target | Static hosting (CDN) | Vercel/Node.js/Edge | Various platforms | Various adapters | Static 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