Sitemap
Buwaneka Sumanasekara

Buwaneka Sumanasekara

Tech Lead | React Native | React JS | Next JS | Remix | Laravel

Atomic Design Meets Feature-Based Architecture in Next.js: A Practical Guide

3 min readJan 10, 2026

--

How to build scalable, maintainable frontends by combining two powerful patterns

Press enter or click to view image in full size

The Problem with Pure Atomic Design

Atomic Design, introduced by Brad Frost, gives us a brilliant vocabulary for UI components: atoms, molecules, organisms, templates, and pages. It works beautifully for design systems and component libraries.

But here’s the catch: it doesn’t tell us where to put business logic.

When you’re building a real application with data fetching, state management, and complex user flows, you’ll inevitably ask: Where does my API call go? Which component owns this state?

Pure atomic design leaves these questions unanswered.

The Problem with Pure Feature-Based Architecture

Feature-based architecture organizes code by business domain: /features/auth, /features/checkout, /features/products. Each feature owns its components, hooks, and logic.

This works great for business logic, but creates a different problem: component duplication.

Get Buwaneka Sumanasekara’s stories in your inbox

Join Medium for free to get updates from this writer.

That Button component? You'll find three slightly different versions across three features. Your Card component exists in five places with minor variations.

Solution: Hybrid Approach

Defined feature based approach inside atomic design’s molecules and organisms. Thats it !!

How should I made the decision ?

Press enter or click to view image in full size

Quick Decision Rules (If you not get it)

  1. “Is this component used in 2+ features?”
    YES → shared/ folder
    NO → [feature]/ folder
  2. “Could this component be useful elsewhere?”
    If unsure, start in feature folder
    Move to shared/ when actually needed (YAGNI principle)
  3. “Is this component tightly coupled to feature logic?”
    YES → Feature folder (even if potentially reusable)
    NO → Consider shared folder

Sample Codes

ItemCard (feature molecule)

/*
* @file item-card.tsx
* @description
* @created Sat Jan 10 2026
* @author Buwaneka
*/

// molecules/product/item-card.tsx
// Feature-specific molecule - only used in product listings

import { OptimizedImage } from '@/components/atoms/optimized-image';
import { Typography } from '@/components/atoms/typography';
import { Badge } from '@/components/atoms/badge';
import { PriceTag } from '@/components/molecules/shared/price-tag';

export function ItemCard({ product }: ItemCardProps) {
return (
<div className="...">
<OptimizedImage src={product.image} alt={product.name} />
<Typography variant="h4">{product.name}</Typography>
<PriceTag price={product.price} discount={product.discount} />
{product.isNew && <Badge variant="new">New</Badge>}
</div>
);
}

Header (shared organism)

/*
* @file header.tsx
* @description
* @created Sat Jan 10 2026
* @author Buwaneka
*/

// organisms/shared/header.tsx
// Shared organism - used across all pages

import { Logo } from '@/components/molecules/shared/logo-section';
import { SearchBar } from '@/components/molecules/shared/search-bar';
import { NavItem } from '@/components/molecules/shared/nav-item';
import { LanguageSwitcher } from '@/components/molecules/shared/language-switcher';

export function Header() {
return (
<header className="...">
<Logo />
<nav>
<NavItem href="/products">Products</NavItem>
<NavItem href="/categories">Categories</NavItem>
</nav>
<SearchBar />
<LanguageSwitcher />
</header>
);
}

CartDrawer (Feature organism)

/*
* @file card-drawer.tsx
* @description
* @created Sat Jan 10 2026
* @author Buwaneka
*/

// organisms/cart/cart-drawer.tsx
// Feature-specific organism - only used in cart feature

import { Button } from '@/components/atoms/button';
import { Typography } from '@/components/atoms/typography';
import { CartItem } from '@/components/molecules/cart/cart-item';
import { CartSummaryRow } from '@/components/molecules/cart/cart-summary-row';

export function CartDrawer({ isOpen, onClose }: CartDrawerProps) {
return (
<aside className="...">
<Typography variant="h2">Your Cart</Typography>
<div className="cart-items">
{items.map(item => <CartItem key={item.id} {...item} />)}
</div>
<CartSummaryRow label="Subtotal" value={subtotal} />
<Button onClick={handleCheckout}>Checkout</Button>
</aside>
);
}

--

--

Buwaneka Sumanasekara
Buwaneka Sumanasekara

Tech Lead | React Native | React JS | Next JS | Remix | Laravel