Configurator Architecture Principles
Interactive 3D configurator frontends require clean separation of spatial bindings, application state, and domain pricing logic.
Establishing unified architecture patterns ensures applications remain maintainable and scalable, regardless of the underlying web UI framework (React, Vue, or Vanilla JS).
Configurator Project Directory Layout#
A consistent directory layout prevents project bloat, ensures code is discoverable, and enables reliable code-generation steps.
Regardless of the framework used, projects must structure files into logical concern directories to avoid nesting conflicts.
Example Directory Hierarchy
Below is a gold-standard directory structure of a completed Aircada React customization overlay application. Use this layout as a reference when scaffolding projects or generating new files.
src/
├── App.tsx # Router & provider entry point
├── index.css # Tailwind import & CSS theme variables
├── store.config.ts # Aircada store initial state configuration
├── components/ # Visual UI components, selectors, and steps
│ ├── UIOverlay.tsx # Absolute layout page containing viewport & controls
│ ├── OptionSwatch.tsx # Color/material selector circular swatches
│ ├── PriceDisplay.tsx # Dynamic live pricing tag with active delta
│ ├── StepPanel.tsx # Multi-step configurator wizard controller
│ └── ViewportControls.tsx # Overlaid viewport controls (e.g. resets)
├── hooks/ # Custom hook subscriptions and state wrappers
│ ├── useOption.ts # Hook wrapping OptionsRegistry sets
│ └── usePrice.ts # Hook reading dynamic checkout calculations
├── registry/ # CLI generated type registries
│ ├── media.generated.ts # Dataset row types and cloud IDs
│ └── options.generated.ts # Option sets models definitions
└── styles/ # Global token styling parameters
└── theme.css # Custom font rules and typography classes- /components: All visual UI elements, option selectors, layout panels, and overlays (e.g. Swatch.tsx, UIOverlay.tsx).
- /hooks or /composables: Custom hooks/composables for subscribing to options, state, or scene operators.
- /lib: Core library instantiations, API connectors, client initializations, and utilities.
- /types: Canonical types and interfaces, typically derived or generated from schemas.
- /styles: Root styles, design token files, and framework-specific utility theme sheets.
Configurator Separation of Concerns#
Interactive 3D configurators must maintain clean separation between layers. Blending state management, 3D manipulation, and UI rendering into single files leads to massive, unmaintainable structures.
| Layer | Responsibilities | Allowed Imports | Prohibitions |
|---|---|---|---|
| UI Layer | Render layout, option buttons, pricing text, and UI states. | Store hooks, types, configuration metadata. | Directly modifying 3D viewport, local state mirrors, executing transactions. |
| State Layer | Read/write application options, price, and active settings. | SDK store APIs, schema enums, generated option registries. | Direct DOM manipulation, styling definitions, viewport instances. |
| Scene Binding | Synchronize store options with 3D models, materials, and animations. | SDK scene APIs, store events, property definitions. | Rendering DOM UI overlays, managing shopping cart items. |
| Pricing/Domain Logic | Calculate complex rules, cart discounts, and option combinations. | Apex schemas, datasets, purchaser plugins. | Subscribing to UI component lifecycles or viewport canvas events. |
Configurator Option State Source of Truth#
In Aircada configurators, the global SDK store is the single source of truth for option states. Storing options in local component state (useState) and attempting to sync them with the store leads to synchronization lag, double renders, and cart valuation errors.
- Store is Truth: All options must be read directly from the store, and changes must write directly to the store via standard patch triggers.
- Exceptions: Transient UI-only states (e.g., hover effects, keyboard focus, loading spin states) are permitted to live in component-local state.
- 3D Synchronization: The store state automatically drives the 3D scene engine and purchaser operations. Bypassing the store breaks scene-object updates.
// ❌ INCORRECT: Mirroring option state in local useState
function BadOptionSelector() {
const [selectedColor, setSelectedColor] = useState(storeState.bodyColor);
const handleChange = (newColor) => {
setSelectedColor(newColor); // Parallel local state
patchStore({ bodyColor: newColor });
};
}
// 🟢 CORRECT: Reading and updating option directly from store
function GoodOptionSelector() {
const selectedColor = useOption("bodyColor");
const handleChange = (newColor) => {
patchStore({ bodyColor: newColor }); // Direct write, updates UI via store listener
};
}UI Component Boundaries#
Keeping a balance between monolithic components (e.g. 1000 lines of inline code) and over-fragmentation (e.g. one component per wrapper div) is essential for code readability and compiler efficiency.
- Extract when: The component is reused across multiple files.
- Extract when: The component carries its own internal UI-only state or complex event handlers.
- Extract when: An inline JSX block exceeds approximately 40-50 lines of code.
- Extract when: The component corresponds 1:1 to a configurator primitive defined in the design system.
- Anti-pattern: Do not define helper components inside another component's file. Maintain the one-component-one-file rule.
- Anti-pattern: Do not create single-use abstractions that add wrapper overhead without logic isolation.






