Skip to main content
The Scope Rule provides a clear, systematic approach to determining where components should live in your project. This guide walks you through the decision-making process with real examples.

The Core Principle

“Scope determines structure” This fundamental rule drives all placement decisions:
  • Code used by 2+ features → MUST go in global/shared directories
  • Code used by 1 feature → MUST stay local in that feature
  • NO EXCEPTIONS - This rule is absolute and non-negotiable

Decision Framework

1

Count the Usage

Identify exactly how many features use the component. Be precise and systematic:
  • Review all imports of the component
  • Count unique feature directories that import it
  • Don’t count multiple components within the same feature as separate uses
Example:
// If ProductCard is imported by:
// - src/features/shop/products/page.tsx
// - src/features/shop/cart/page.tsx
// - src/features/wishlist/page.tsx

// That's 2 features (shop + wishlist) → Goes in shared/
2

Apply the Scope Rule

Based on your count, apply the rule strictly:1 Feature = Local Placement
src/features/shop/
  products/
    components/
      product-filter.tsx  # Only used by products
2+ Features = Shared Placement
src/shared/
  components/
    product-card.tsx  # Used by shop, cart, wishlist
3

Validate Against Framework Patterns

Ensure your placement aligns with framework-specific best practices:
  • All components MUST be standalone (Angular 20+)
  • Use ChangeDetectionStrategy.OnPush for all components
  • Leverage signals with signal(), computed(), and effect()
  • Place signal stores locally or globally based on scope
// Local signal store: src/features/shop/signals/cart.signal.ts
import { signal, computed } from '@angular/core';

const _cartItems = signal<CartItem[]>([]);
export const cartItems = _cartItems.asReadonly();
export const cartTotal = computed(() => 
  _cartItems().reduce((sum, item) => sum + item.price, 0)
);
4

Document Your Decision

Explain WHY the placement was chosen. This creates shared understanding:
/**
 * ProductCard - Shared Component
 * 
 * Used by:
 * - Shop feature (product listing)
 * - Cart feature (cart items display)
 * - Wishlist feature (saved items)
 * 
 * Placement: /src/shared/components/product-card.tsx
 * Reason: Used by 3 features (2+ rule) → Must be in shared/
 */
export function ProductCard({ product }: Props) {
  // Implementation
}
5

Verify Future-Proofing

Ask these final validation questions:
  • Can a new developer understand the app structure immediately?
  • Will this placement scale as features grow?
  • Does the structure “scream” what the application does?
  • Are you following framework-specific optimizations?

Real-World Examples

Example 1: Authentication Component

Scenario: SocialLogin component used in both login and register pages. Analysis:
Used by:
- features/auth/login/
- features/auth/register/

Count: 2 sub-routes within same auth feature = 1 feature
Decision: Place in features/auth/components/social-login.tsx
Reasoning: Both login and register are part of the same authentication feature. The component is shared within that feature boundary, not across multiple features.

Example 2: UI Button Component

Scenario: Button component used throughout the application. Analysis:
Used by:
- features/auth/ (login, register)
- features/shop/ (add to cart, checkout)
- features/profile/ (edit profile, settings)

Count: 3 distinct features
Decision: Place in shared/components/ui/button.tsx
Reasoning: Used by 2+ features (actually 3), so it must go in shared directory. This is a clear-cut case for global placement.

Example 3: Next.js Server Action

Scenario: productActions used only within shop feature. Analysis:
// app/(shop)/_actions/product-actions.ts
import "server-only";

export async function addToCart(productId: string) {
  // Server-side mutation logic
}

// Used only by:
// - app/(shop)/products/page.tsx
// - app/(shop)/products/[slug]/page.tsx

Count: 1 feature (shop)
Decision: Keep in (shop)/_actions/
Reasoning: Server Actions are scoped to the shop feature. Even though used by multiple pages, they’re all within the same route group.

Edge Cases and Special Considerations

Start local, refactor when needed.When uncertain:
  1. Place the component locally within the feature
  2. Add a comment noting potential for extraction
  3. When a second feature needs it, move to shared/
  4. Update all imports
This prevents premature optimization while maintaining flexibility.
Use private folders for co-location.
app/
  (shop)/
    products/
      _components/       # Only for products route
        product-filter.tsx
    cart/
      _components/       # Only for cart route
        cart-summary.tsx
    _components/         # Shared within shop feature
      shop-header.tsx
Private folders (_components) keep route-specific code co-located without polluting the routing system.
Static by default, islands only when absolutely necessary.Use client islands when you need:
  • User interaction (forms, buttons that mutate state)
  • Real-time updates (live chat, notifications)
  • Client-side state management
<!-- Static: no interactivity needed -->
<ProductCard title="Product" price="$99" />

<!-- Island: needs user interaction -->
<AddToCart client:load productId="123" />
Placement rule applies equally to both static components and islands.
Apply the same scope rule to state management.
// Local: features/shop/signals/cart.signal.ts
// Only shop feature needs cart state
const _cartState = signal<CartState>({ items: [] });

// Shared: shared/signals/theme.signal.ts
// Multiple features need theme state
const _themeState = signal<Theme>('light');
State management follows the same scoping rules as components.
Same rule applies to all code, not just components.
// Local utility: features/shop/utils/price-formatter.ts
export function formatPrice(price: number) {
  // Only used within shop feature
}

// Shared utility: shared/utils/date-formatter.ts
export function formatDate(date: Date) {
  // Used by blog, shop, dashboard
}

Quality Checklist

Before finalizing any placement decision, verify:
  • Scope verification: Have you correctly counted feature usage?
  • Framework compliance: Are you using framework-specific best practices?
  • Naming validation: Do component names follow conventions?
  • Screaming test: Can someone understand the structure at a glance?
  • Performance: Are you optimizing for bundle size and rendering?
  • Future-proofing: Will this scale as the application grows?

Next Steps

Project Setup

Learn how to set up new projects with proper structure from the start

Migration Guide

Restructure existing projects to follow the Scope Rule

Best Practices

Quality checks and patterns for maintaining clean architecture

Agents Reference

Explore framework-specific agents for automated guidance

Build docs developers (and LLMs) love