---
name: salesforce-frontend-architecture
description: Architect-level guidance for Salesforce UI solutions — choosing between declarative (Flow, Dynamic Forms, App Builder), LWC, Experience Cloud (LWR vs Aura), Lightning Out / LWC on external sites, and off-platform apps (React/Angular) talking to Salesforce via API; org-wide component library and design system strategy; performance and governor-limit-aware UI data patterns; state management across LWCs; packaging (unlocked/2GP), DevOps, and Aura-to-LWC migration. Use this whenever the user asks how to architect a Salesforce app UI, whether to build in LWC or externally, how to structure a Salesforce frontend codebase, how to share components across teams/orgs, how to migrate from Aura or Visualforce, or how to design Experience Cloud sites. Also apply it when reviewing Salesforce UI designs.
metadata:
  technology: Salesforce
  type: architecture
---

# Salesforce Frontend Architecture

The first architectural question on Salesforce is not "which framework" but **"how much code at all?"** The platform rewards declarative-first, and every custom component is something the org must maintain through three releases a year.

## 1. Build-vs-configure ladder

Walk the requirement down this ladder; stop at the first rung that fits.

1. **Standard UI + page layout / Dynamic Forms / Dynamic Actions** — field visibility rules, conditional sections, record pages.
2. **Screen Flows** — guided multi-step processes, wizards, approvals. Combine with **LWC in Flow** for one custom step rather than rebuilding the wizard in code.
3. **Lightning App Builder with standard + AppExchange components** — dashboards, related lists, tabs.
4. **Custom LWC on-platform** — bespoke UX, complex interaction, performance-critical lists, integrations surfaced in UI.
5. **Experience Cloud (LWR)** — external users, portals, public sites.
6. **Off-platform app** (React/Angular/Next.js) calling Salesforce via **REST/GraphQL API, Pub/Sub API, or Data Cloud** — when UX requirements exceed what Lightning can render (highly branded consumer apps, heavy real-time, non-Salesforce identity), or when the majority of data lives elsewhere.
7. **Lightning Out / LWC in external containers** — legacy; use only for embedding a few components in an existing web property.

Document the chosen rung and *why the lower rungs were rejected*. Architects get challenged on this; the written rationale is the answer.

## 2. On-platform codebase structure

```
force-app/main/default/
├── lwc/
│   ├── <feature>*/            # naming: <domain><Component> e.g. orderList, orderDetail
│   ├── shared*/               # sharedUtils, sharedStyles, sharedConstants — service-only components
│   └── ds*/                   # design-system wrappers (dsButtonBar, dsPageHeader)
├── classes/
│   ├── <Domain>Controller.cls # @AuraEnabled façade for LWC — thin, no logic
│   ├── <Domain>Service.cls    # business logic, sharing decisions
│   ├── <Domain>Selector.cls   # SOQL (fflib-style), USER_MODE
│   └── *Test.cls
├── messageChannels/           # LMS channels
├── customLabels/, staticresources/, flexipages/, flows/
```

- **Controller/Service/Selector separation** keeps `@AuraEnabled` methods to one line each and lets services be reused by Flow (`@InvocableMethod`), triggers, and batch.
- **Multi-package orgs**: split into unlocked packages by domain (`core-ui`, `sales-orders`, `service-console`) with explicit dependencies; a `design-system` package that every other UI package depends on.
- One `.forceignore`, one `sfdx-project.json` with `packageDirectories` per package; CI validates each package independently.

## 3. Component library and design system

- A dedicated **`ds*` / `ui*` component set** wrapping Lightning Base Components with the org's UX patterns (page header with breadcrumbs, empty state, error panel, confirmation modal via `lightning/modal`, filter bar). Product teams compose from these; they don't restyle base components.
- **SLDS 2 styling hooks** and a **theme** (Lightning Theme + branding tokens for Experience Cloud) as the single source of visual truth; no per-component colour literals.
- Publish the library as an **unlocked package** with a versioned changelog; a **Storybook-like showcase page** (an internal Lightning app with one tab per component) so teams can see states.
- Governance: an ADR per new shared component, a code owner group, and a deprecation policy (announce → warn via JSDoc `@deprecated` → remove two releases later).

## 4. Data and state across components

| Scenario | Pattern |
|---|---|
| Same record shown in several components on a page | Each uses `@wire(getRecord)` — LDS caches once; `notifyRecordUpdateAvailable` after Apex mutations |
| Components on one page need to coordinate (filter → list → detail) | Parent container owns state, children are presentational; **Lightning Message Service** only when components are in separate regions/apps |
| Cross-tab / cross-app coordination (console) | LMS with `APPLICATION` scope + `platformWorkspaceApi` |
| Server push (record changed by another user, integration) | **Platform Events** / **Change Data Capture** via `lightning/empApi`; fall back to polling only where empApi is unavailable |
| Large lists | Server-side pagination in Apex Selector; `lightning-datatable` with `enable-infinite-loading`; never client-side filtering of > 2 000 rows |
| Reference/config data | `getObjectInfo`, `getPicklistValues`, Custom Metadata via cacheable Apex; cache in a shared service component |
| Expensive computed views | Apex cacheable with **Platform Cache** (`Cache.Org`/`Cache.Session`) partition for cross-user reuse |

Avoid "one giant Apex call returns the whole page" — it defeats LDS caching and fails on the first governor limit. Prefer several small cacheable wires that the platform parallelises.

## 5. Performance and limits — UI implications

- Lightning page load budget: aim for **EPT < 3 s** on record pages; measure with **Lightning Usage App** and the **Salesforce Community Page Optimizer** / Lightning Page Performance in App Builder.
- Fewer components per page beats fewer lines per component — each LWC region is a separate render/data cycle. Collapse rarely-used sections into tabs or accordions (lazy rendered).
- Apex per transaction: 100 SOQL, 150 DML, 10 s CPU, 6 MB heap (12 MB async) — design Selectors to aggregate in SOQL (`GROUP BY`) rather than in Apex loops.
- API-heavy off-platform apps: use **Composite API / GraphQL** to reduce round trips, respect API request limits, cache with ETags.
- Experience Cloud (LWR): CDN on, minimal third-party scripts, images sized, guest-user data via cacheable Apex with careful sharing.

## 6. Experience Cloud

- **LWR** for all new sites (performance, modern LWC, no Aura); **Aura templates** only when a required standard component isn't on LWR yet — check the release-notes gap list and document it.
- Guest user: everything is public — sharing rules on guest profile, `WITHOUT SHARING` never in guest-facing controllers, USER_MODE everywhere.
- Authenticated external users: Login/Identity (SSO, MFA), Person Accounts or Contacts under Accounts — decide the data model early; it drives sharing sets/groups.
- Branding via **Theme Layouts** + styling hooks; component visibility via **audiences**; SEO via `pageTitle`/`meta` on LWR pages.
- Custom domain, CSP Trusted Sites, and CORS configured from the start.

## 7. Off-platform apps talking to Salesforce

- **Auth**: OAuth 2.0 PKCE (public clients), JWT Bearer (server-to-server), or **External Client Apps** (successor to Connected Apps). Never embed a Connected App secret in a browser bundle.
- **Data**: REST `sobjects`/`query`, **GraphQL API** for shaped reads, **Composite** for multi-record writes, **Pub/Sub API** (gRPC) for events, **Bulk API 2.0** for volume.
- **Backend-for-frontend** layer (Node/Java) between the browser and Salesforce to hold tokens, aggregate calls, and cache — the browser talks to your BFF, not directly to Salesforce.
- Keep field-level logic (validation, picklists) in Salesforce metadata and read it via `describe`/UI API so the external UI doesn't drift from the org.
- Treat Salesforce API limits as a shared org resource — one runaway frontend can starve integrations. Budget and monitor.

## 8. Migration playbooks

**Aura → LWC**
1. Inventory Aura components; classify: replace-with-standard, rewrite-as-LWC, retire.
2. Convert leaves first (LWC can live inside Aura, not vice versa).
3. Replace `aura:method`/events with `@api` methods, `CustomEvent`, LMS.
4. Replace `$A.enqueueAction` with `@wire`/imperative Apex; make Apex `cacheable` where possible.
5. Retire Aura wrappers last; measure EPT before/after.

**Visualforce → LWC**
1. Split by user-facing function; many VF pages become Flows + Dynamic Forms with zero code.
2. Remaining custom UI → LWC quick actions/record page components; PDF rendering stays on VF (`renderAs="pdf"`) or moves to a document service.
3. Controllers → Service/Selector layers reused by LWC controllers.

## 9. DevOps and quality gates

- Source-tracked development in **scratch orgs** (or sandboxes with source tracking); feature branches → PR → CI runs `sf project deploy validate`, Jest, Apex tests (≥ 85 % coverage on changed classes), PMD, ESLint, sa11y.
- Unlocked package versions per release; promote through sandboxes; **DevOps Center**, Gearset, Copado, or GitHub Actions + `sf` CLI.
- Release readiness: run the org's LWC Jest suite and Apex tests against each **Salesforce pre-release** sandbox (Spring/Summer/Winter) — API and Locker/LWS changes break things three times a year.
- Observability: Nebula Logger (or equivalent) for Apex; `console.error` funnelled to a logging Platform Event from LWC; Event Monitoring for EPT trends.

## 10. Architecture decision record (deliver this)

```
# ADR-<n>: <UI decision>
## Context — user need, volumes, users (internal/external/guest), devices
## Decision — rung on the ladder; components/packages; data patterns
## Alternatives rejected — one line each, with the killer reason
## Consequences — limits watched, ops cost, migration path
## Review date
```

## Anti-patterns to flag

- Custom LWC rebuilding a Flow or a record page section that Dynamic Forms handles.
- Aura or Visualforce for new work.
- One mega-controller returning page-shaped JSON; SObjects returned raw to the browser.
- Business logic in `@AuraEnabled` methods instead of a Service layer.
- Experience Cloud on Aura template started this year without a documented reason.
- Browser calling Salesforce APIs directly with a client secret.
- No design-system layer — every team restyling `lightning-button`.
- Skipping pre-release sandbox testing.
