Files
sam-react-prod/claudedocs/architecture/[RESEARCH-2026-02-11] ERP-admin-panel-architecture-patterns.md
유병철 113d82c254 chore(WEB): package-lock.json git 트래킹 제거
- .gitignore에 이미 등록되어 있으나 과거 커밋으로 트래킹 중이던 파일 제거

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 20:13:31 +09:00

23 KiB

Research: Next.js / React ERP & Admin Panel Architecture Patterns (2025-2026)

Date: 2026-02-11 Purpose: Compare SAM ERP's current architecture against proven open-source patterns Confidence: High (0.85) - Based on 6 major open-source projects and established methodologies


Executive Summary

After investigating 6 major open-source admin/ERP frameworks and 3 architectural methodologies, the dominant pattern emerging in 2025-2026 is a hybrid approach: domain/feature-based folder organization combined with headless CRUD hooks and a provider-based API abstraction layer. Pure Atomic Design is losing ground to Feature-Sliced Design (FSD) for application-level organization, though Atomic Design remains useful for the shared UI component layer.

Key Findings

  1. Resource-based CRUD abstraction (react-admin, Refine) is the most proven pattern for 50+ page admin apps
  2. Feature/domain-based folder structure is winning over layer-based (atoms/molecules/organisms) for application code
  3. Provider pattern (dataProvider, authProvider) decouples UI from API more effectively than scattered Server Actions
  4. Config-driven UI generation (Payload CMS) reduces code duplication for similar pages
  5. Headless hooks (useListController, useTable, useForm) separate business logic from UI completely

1. Project-by-Project Architecture Analysis

1.1 React-Admin (marmelab) -- 25K+ GitHub Stars

Architecture: Resource-based SPA with Provider pattern

Key Concepts:

  • Resources: The core abstraction. Each entity (posts, users, orders) is a "resource" with CRUD views
  • Providers: Adapter layer between UI and backend
    • dataProvider - abstracts all API calls (getList, getOne, create, update, delete)
    • authProvider - handles authentication flow
    • i18nProvider - internationalization
  • Headless Core: ra-core package contains all hooks, zero UI dependency
  • Controller Hooks: useListController, useEditController, useCreateController, useShowController

Folder Pattern:

src/
  resources/
    posts/
      PostList.tsx        # <List> view
      PostEdit.tsx         # <Edit> view
      PostCreate.tsx       # <Create> view
      PostShow.tsx         # <Show> view
    users/
      UserList.tsx
      UserEdit.tsx
  providers/
    dataProvider.ts        # API abstraction
    authProvider.ts        # Auth abstraction
  App.tsx                  # Resource registration

CRUD Registration Pattern:

<Admin dataProvider={dataProvider} authProvider={authProvider}>
  <Resource name="posts" list={PostList} edit={PostEdit} create={PostCreate} />
  <Resource name="users" list={UserList} edit={UserEdit} />
</Admin>

SAM Comparison:

Aspect react-admin SAM ERP
API Layer Centralized dataProvider 89 scattered actions.ts files
CRUD Views Resource-based registration Manual page creation per domain
State React Query (built-in) Zustand + manual fetching
Form react-hook-form (built-in) Mixed (migrating to RHF+Zod)

Sources:


1.2 Refine -- 30K+ GitHub Stars

Architecture: Headless meta-framework with resource-based CRUD

Key Concepts:

  • Headless by design: Zero UI opinion, works with Ant Design, Material UI, Shadcn, or custom
  • Data Provider Interface: Standardized CRUD methods (getList, getOne, create, update, deleteOne)
  • Resource Hooks: useTable, useForm, useShow, useSelect -- all headless
  • Inferencer: Auto-generates CRUD pages from API schema

Data Provider Interface:

const dataProvider = {
  getList:    ({ resource, pagination, sorters, filters }) => Promise,
  getOne:     ({ resource, id }) => Promise,
  create:     ({ resource, variables }) => Promise,
  update:     ({ resource, id, variables }) => Promise,
  deleteOne:  ({ resource, id }) => Promise,
  getMany:    ({ resource, ids }) => Promise,
  custom:     ({ url, method, payload }) => Promise,
};

Headless Hook Pattern:

// useTable returns data + controls, you handle UI
const { tableProps, sorters, filters } = useTable({ resource: "products" });

// useForm returns form state + submit, you handle UI
const { formProps, saveButtonProps } = useForm({ resource: "products", action: "create" });

SAM Comparison:

Aspect Refine SAM ERP
API Abstraction Single dataProvider Per-domain actions.ts
List Page useTable hook UniversalListPage template
Form useForm hook (headless) Manual per-page forms
Code Generation Inferencer auto-gen Manual creation

Sources:


1.3 Payload CMS 3.0 -- 30K+ GitHub Stars

Architecture: Config-driven, Next.js-native with auto-generated admin UI

Key Concepts:

  • Collection Config: Define schema once, get admin UI + API + types automatically
  • Field System: Rich field types auto-generate corresponding UI components
  • Hooks: beforeChange, afterRead, beforeValidate at collection and field level
  • Access Control: Document-level and field-level permissions in config
  • Next.js Native: Installs directly into /app folder, uses Server Components

Config-Driven Pattern:

// collections/Products.ts
export const Products: CollectionConfig = {
  slug: 'products',
  admin: {
    useAsTitle: 'name',
    defaultColumns: ['name', 'price', 'status'],
  },
  access: {
    read: () => true,
    create: isAdmin,
    update: isAdminOrSelf,
  },
  hooks: {
    beforeChange: [calculateTotal],
    afterRead: [formatCurrency],
  },
  fields: [
    { name: 'name', type: 'text', required: true },
    { name: 'price', type: 'number', min: 0 },
    { name: 'status', type: 'select', options: ['draft', 'published'] },
    { name: 'category', type: 'relationship', relationTo: 'categories' },
  ],
};

SAM Comparison:

Aspect Payload CMS SAM ERP
Page Generation Auto from config Manual per page
Field Definitions Centralized schema Inline JSX per form
Access Control Config-based per field Manual per component
Type Safety Auto-generated from schema Manual interface definitions

Sources:


1.4 Medusa Admin v2 -- 26K+ GitHub Stars

Architecture: Domain-based routes with widget injection system

Key Concepts:

  • Domain Routes: Routes organized by business domain (products, orders, customers)
  • Widget System: Inject custom React components into predetermined zones
  • UI Routes: File-based routing under src/admin/routes/
  • Hook-based data fetching: Domain-specific hooks for API integration
  • Monorepo: UI library (@medusajs/ui) separate from admin logic

Folder Structure:

packages/admin/dashboard/src/
  routes/
    products/
      product-list/
        components/
        hooks/
        page.tsx
      product-detail/
        components/
        hooks/
        page.tsx
    orders/
      order-list/
      order-detail/
    customers/
  hooks/              # Shared hooks
  components/         # Shared components
  lib/                # Utilities

SAM Comparison:

Aspect Medusa Admin SAM ERP
Route Organization Domain > Action > Components Domain > page.tsx + actions.ts
Shared Components Separate UI package organisms/molecules/atoms
Hooks Per-route + shared Global + inline
Extensibility Widget injection zones N/A

Sources:


1.5 AdminJS

Architecture: Auto-generated admin from resource configuration

Key Concepts:

  • Resource Registration: Register database models, get admin UI automatically
  • Component Customization: Override via ComponentLoader
  • Dashboard Customization: Custom React components for dashboard

SAM Relevance: Lower -- AdminJS is more backend-driven (Node.js ORM-based) and less applicable to a frontend-heavy ERP.

Sources:


1.6 Hoppscotch

Architecture: Monorepo with shared-library pattern

Key Concepts:

  • @hoppscotch/common: 90% of UI and business logic in shared package
  • @hoppscotch/data: Type safety across all layers
  • Platform-specific code: Thin wrapper handling native capabilities

SAM Relevance: The shared-library-as-core pattern is interesting for large codebases where most logic is platform-agnostic.

Sources:


2. Architectural Methodologies Comparison

2.1 Feature-Sliced Design (FSD) -- Rising Standard

7-Layer Architecture:

app/        # App initialization, providers, routing
processes/  # Complex cross-page business flows (deprecated in latest)
pages/      # Full page compositions
widgets/    # Self-contained UI blocks with business logic
features/   # User-facing actions (login, add-to-cart)
entities/   # Business entities (user, product, order)
shared/     # Reusable utilities, UI kit, configs

Key Rules:

  • Layers can ONLY import from layers below them
  • Each layer divided into slices (domain groupings)
  • Each slice divided into segments (ui/, model/, api/, lib/, config/)

FSD Applied to ERP:

src/
  app/                          # App shell, providers
  pages/
    quality-qms/                # QMS page composition
    sales-quote/                # Quote page composition
  widgets/
    inspection-report/          # Self-contained inspection UI
      ui/
      model/
      api/
    quote-calculator/
  features/
    add-inspection-item/
    approve-quote/
  entities/
    inspection/
      ui/    (InspectionCard, InspectionRow)
      model/ (types, store)
      api/   (getInspection, updateInspection)
    quote/
      ui/
      model/
      api/
  shared/
    ui/     (Button, Table, Modal -- your atoms)
    lib/    (formatDate, exportUtils)
    api/    (httpClient, apiProxy)
    config/ (constants)

Sources:


2.2 Atomic Design -- Aging for App-Level Organization

SAM's Current Approach:

components/
  atoms/       # Basic UI elements
  molecules/   # (unused)
  organisms/   # Complex composed components
  templates/   # Page layout templates

Industry Assessment (2025-2026):

  • Atomic Design excels for UI component libraries (shared/ layer)
  • Struggles with domain complexity -- "UserCard" and "ProductCard" are both organisms but semantically distinct
  • Grouping by visual complexity (atom/molecule/organism) dilutes domain boundaries
  • Most large-scale projects have moved to feature/domain organization for application code
  • Atomic Design remains valuable for the shared UI kit layer only

Sources:


2.3 Modular Monolith (Frontend)

Key Principles for ERP:

  • Single deployment, but internally organized as independent modules
  • Each module = bounded context with clear API boundaries
  • Modules communicate through well-defined interfaces, not direct imports
  • Common concerns (auth, logging) handled at application level

Applied to Next.js ERP:

src/
  modules/
    quality/
      components/
      hooks/
      actions/
      types/
      index.ts          # Public API -- only exports from here
    sales/
      components/
      hooks/
      actions/
      types/
      index.ts
    accounting/
      ...
  shared/               # Cross-module utilities
  app/                  # Next.js routing (thin layer)

Sources:


3. Server Actions Organization Patterns

Pattern A: Colocated (SAM's Current -- 89 files)

app/[locale]/(protected)/quality/qms/
  page.tsx
  actions.ts        # Server actions for this route

Pros: Easy to find, clear ownership Cons: Duplication across similar pages, no reuse

Pattern B: Domain-Centralized (react-admin / Refine style)

src/
  actions/
    quality/
      inspection.ts     # All inspection-related server actions
      qms.ts
    sales/
      quote.ts
      order.ts
  lib/
    api-client.ts       # Shared fetch logic with auth

Pros: Reusable across pages, easier to maintain Cons: Indirection, harder to find for route-specific logic

app/[locale]/(protected)/quality/qms/
  page.tsx
  _actions.ts          # Route-specific actions only

src/
  domains/
    quality/
      actions/          # Shared domain actions
        inspection.ts
        qms.ts
      hooks/
      types/

Pros: Route-specific stays colocated, shared logic centralized Cons: Need clear rules on what goes where

Industry Consensus

For 100+ page apps, the hybrid approach (Pattern C) dominates. Route-specific logic stays colocated; shared domain logic is centralized. The key is having a clear data provider / API client layer that all server actions delegate to.

Sources:


4. CRUD Abstraction Patterns for 50+ Similar Pages

Pattern 1: Resource Hooks (react-admin / Refine approach)

// hooks/useResourceList.ts
function useResourceList<T>(resource: string, options?: ListOptions) {
  const [data, setData] = useState<T[]>([]);
  const [pagination, setPagination] = useState({ page: 1, pageSize: 20 });
  const [filters, setFilters] = useState({});
  const [sorters, setSorters] = useState({});

  useEffect(() => {
    fetchList(resource, { pagination, filters, sorters })
      .then(result => setData(result.data));
  }, [resource, pagination, filters, sorters]);

  return { data, pagination, setPagination, filters, setFilters, sorters, setSorters };
}

// Usage in any list page
function QualityInspectionList() {
  const { data, pagination, filters } = useResourceList<Inspection>('quality/inspections');
  return <UniversalListPage data={data} columns={inspectionColumns} />;
}

Pattern 2: Config-Driven Pages (Payload CMS approach)

// configs/quality-inspection.config.ts
export const inspectionConfig: ResourceConfig = {
  resource: 'quality/inspections',
  list: {
    columns: [
      { key: 'id', label: '번호' },
      { key: 'name', label: '검사명' },
      { key: 'status', label: '상태', render: StatusBadge },
    ],
    filters: [
      { key: 'status', type: 'select', options: statusOptions },
      { key: 'dateRange', type: 'daterange' },
    ],
    defaultSort: { key: 'createdAt', direction: 'desc' },
  },
  form: {
    fields: [
      { name: 'name', type: 'text', required: true, label: '검사명' },
      { name: 'type', type: 'select', options: typeOptions, label: '검사유형' },
    ],
  },
};

// Generic page component
function ResourceListPage({ config }: { config: ResourceConfig }) {
  const list = useResourceList(config.resource);
  return <UniversalListPage {...list} columns={config.list.columns} />;
}

Pattern 3: Template Composition (SAM's current direction, improved)

// templates/UniversalCRUDPage.tsx -- enhanced version
function UniversalCRUDPage<T>({
  resource,
  listConfig,
  detailConfig,
  formConfig,
}: CRUDPageProps<T>) {
  // Handles list/detail/form modes based on URL
  // Integrates data fetching, pagination, filtering
  // Renders appropriate template based on mode
}

Industry Assessment

  • Pattern 1 (Resource Hooks) is the most widely adopted -- used by react-admin (25K stars) and Refine (30K stars)
  • Pattern 2 (Config-Driven) reduces code the most but requires upfront investment in the config system
  • Pattern 3 (Template Composition) is the middle ground -- SAM's UniversalListPage is already this direction

Recommendation: Evolve toward a Provider + Resource Hooks layer. Keep UniversalListPage and IntegratedDetailTemplate but back them with a standardized data provider.


5. Comparison Matrix: SAM ERP vs Industry Patterns

Dimension SAM ERP (Current) react-admin Refine Payload CMS FSD Recommendation
Folder Structure Domain-based (app router) Resource-based Resource-based Collection-based Layer > Slice > Segment Hybrid Domain + FSD shared layer
Component Org Atomic Design (partial) Flat per resource Flat per resource Config-driven Layer-based (entities/features) FSD for app code, Atomic for shared UI
API Layer 89 colocated actions.ts Centralized dataProvider Centralized dataProvider Built-in Local API api/ segment per slice Centralized API client + domain actions
CRUD Abstraction UniversalListPage template Resource + Controller hooks useTable/useForm hooks Auto-generated from config Manual per feature Add resource hooks on top of templates
Form Handling Mixed (migrating to RHF+Zod) react-hook-form (built-in) react-hook-form (headless) Auto from field config Manual per feature Complete RHF+Zod migration
State Management Zustand stores React Query (built-in) React Query (built-in) Server-side Per-slice model/ Keep Zustand for UI state, add React Query for server state
Type Safety Manual interfaces Built-in types TypeScript throughout Auto-generated from schema Manual per segment Consider schema-driven type generation
50+ Page Scale Manual duplication Resource registration Inferencer + hooks Collection config Slice per entity Resource hooks + config-driven columns

6. Actionable Recommendations for SAM ERP

Priority 1: Introduce a Data Provider / API Client Layer

Why: The biggest gap vs. industry standard. 89 scattered actions.ts files means duplicated fetch logic, inconsistent error handling, and no centralized caching.

Action: Create a dataProvider abstraction inspired by react-admin/Refine:

// src/lib/data-provider.ts
export const dataProvider = {
  getList: (resource, params) => proxyFetch(`/api/proxy/${resource}`, params),
  getOne: (resource, id) => proxyFetch(`/api/proxy/${resource}/${id}`),
  create: (resource, data) => proxyFetch(`/api/proxy/${resource}`, { method: 'POST', body: data }),
  update: (resource, id, data) => proxyFetch(`/api/proxy/${resource}/${id}`, { method: 'PUT', body: data }),
  delete: (resource, id) => proxyFetch(`/api/proxy/${resource}/${id}`, { method: 'DELETE' }),
};

Priority 2: Create Resource Hooks

Why: Reduce per-page boilerplate for list/detail/form patterns.

Action: Build useResourceList, useResourceDetail, useResourceForm hooks that wrap the data provider.

Priority 3: Evolve Folder Structure Toward Hybrid FSD

Why: Atomic Design for app-level code leads to unclear domain boundaries.

Action:

  • Keep shared/ui/ (atoms/organisms) for reusable UI components
  • Add domains/ or entities/ for business-logic grouping
  • Keep app/ routes thin -- delegate to domain components

Priority 4: Complete Form Standardization

Why: Mixed form patterns make maintenance harder and prevent reusable form configs.

Action: Complete the react-hook-form + Zod migration. Consider field-config-driven forms (Payload pattern) for highly repetitive forms.

Priority 5: Consider Server State Management (React Query / TanStack Query)

Why: react-admin and Refine both use React Query internally for caching, optimistic updates, and background refetching. Zustand is better suited for client UI state.

Action: Evaluate adding TanStack Query for server state alongside Zustand for UI state.


7. What SAM ERP Is Already Doing Well

  1. Domain-based routing (app/[locale]/(protected)/quality/...) aligns with industry best practice
  2. UniversalListPage + IntegratedDetailTemplate is the right abstraction direction (similar to react-admin's List/Edit components)
  3. SearchableSelectionModal as a reusable pattern is good (similar to react-admin's ReferenceInput)
  4. Server Actions in colocated files follows Next.js official recommendation for route-specific logic
  5. Zustand for global state is a solid choice for UI state (sidebar state, theme, etc.)

Sources

Open-Source Projects

Architectural Methodologies

Folder Structure & Patterns