Skip to content
← Back to projects
Before/after architecture: Salesforce Classic components (Visualforce, JavaScript button, Process Builder, 8 profiles, Custom Settings, unsecured Apex) and their Lightning Experience replacements (LWC, Quick Action and Screen Flow, Record-Triggered Flow, 5 Permission Set Groups, Named Credentials, Apex with sharing and stripInaccessible)
Reconstructed case study

Salesforce Classic to Lightning Experience Migration (Nova Manufacturing)

Classic to Lightning migration and Apex security hardening for an international industrial equipment manufacturer.

Context

> Reconstructed case study: the scenario, company and figures are fictional. It is inspired by the kind of work I do in my role, whose specifics remain confidential, and does not describe a client engagement. Nova Manufacturing is an international manufacturer of industrial equipment (~150 sales reps spread across 3 sites/hubs in Europe). Its Salesforce CRM, still mostly running on Classic, had accumulated several generations of development: Visualforce pages, JavaScript buttons, Process Builder, heavily customized profiles, and REST integrations with third-party systems (ERP, billing). With Classic being progressively deprecated, the scenario is the modernization of the application — with an explicit mandate: don't just migrate the interface, harden the security of the legacy Apex code along the way, some of which had never been audited since its initial release.

Identified Components & Proposed Solutions

  • Visualforce page Compte360 (Account object): search and display orders and contracts linked to an account, SOQL query built dynamically in the controller → converted to a Lightning Web Component AccountOrdersOverview, wired to AccountOrdersController.getOrders() (6 days)
  • JavaScript button UpdateContractStatus (Contract object): updated contract status with no rights check or user confirmation → replaced by a Quick Action triggering a Screen Flow with an invocable Apex method (3 days)
  • Process Builder "Order Validation": multi-level approval workflow triggered on creation → migrated to a Record-Triggered Flow (separate before-save/after-save to respect Governor Limits) (4 days)
  • 4 legacy Apex controllers: systematic audit, no CRUD/FLS rights testing in place → rebuilt with a strict with sharing pattern. Plus 4 new controllers built for this project, with security designed in from the start (5 days total)
  • Total estimate: 25 days (audit 2d, Visualforce→LWC migration 6d, JS button→Quick Action/Flow 3d, Process Builder→Flow 4d, Apex security 5d, testing 3d, documentation 2d), plus 8h of user training

Delivered Development

  • LWC AccountOrdersOverview: lightning-datatable with server-side pagination and keyword search, wired to an @AuraEnabled(cacheable=true) Apex method using WITH SECURITY_ENFORCED
  • Record-Triggered Flow "Order Validation v2": bulkified logic processing the full collection (the old Process Builder wasn't, and caused Governor Limit errors on bulk imports), with a reusable sub-flow for the notification step
  • Permission Set Groups: consolidated 8 near-duplicate legacy profiles into 5 groups, reducing configuration debt and clarifying access audits
  • Named Credentials: replaced hardcoded REST endpoints (credentials previously visible in Custom Settings to any admin) with Named Credentials, authentication managed by the platform

Security & Best Practices

  • ContractService.updateStatus(): before the rebuild, it wrote directly to Contract with no checks, and the class ran without sharing by default from an old Visualforce controller. After: Security.stripInaccessible(AccessType.UPDATABLE, records) before every DML, explicit with sharing on the class
  • Dedicated unit test simulating a user without edit rights on Contract.Statut__c to verify the write is correctly blocked by stripInaccessible()
  • Audit of the 4 legacy controllers: no CRUD/FLS rights test existed before this project — systematic addition of explicit checks wherever queries weren't eligible for WITH SECURITY_ENFORCED. Security designed in from the start on the 4 new controllers built for the project
  • Profile-to-permission-set mapping matrix validated with business managers before cutover, to avoid any silent regression on access rights

Risk Management

  • Legacy Apex controller incompatibility (medium impact) → sandbox regression testing before each cutover, component-by-component deployment rather than a single big-bang release
  • User adoption (high probability) → 8h of training split across 2 sessions, short role-based documentation instead of a single manual
  • Governor Limit errors on migrated Flows (low impact, already hit once in sandbox) → bulkification built in from the design stage, tested with 200+ record datasets
  • Silent access-rights regression during profile consolidation (high impact) → profile-to-permission-set mapping matrix validated with business managers before production rollout

Stack & Technologies

  • Lightning Web Components, Apex, SOQL
  • Flow (Record-Triggered, before-save/after-save)
  • Permission Set Groups, Named Credentials
  • Salesforce Security Model — CRUD/FLS, WITH SECURITY_ENFORCED, stripInaccessible()

Results

8
Apex classes secured
4 legacy hardened + 4 new built secure-by-design
8 → 5
Profiles consolidated
Permission Set Groups
25 days
Total effort
audit + migration + security + testing + documentation
200+ records
Bulk tested
Governor Limits validation on migrated Flows

Known Limitations & Decisions

  • Progressive, component-by-component migration rather than a big bang: slower, but allows a targeted rollback if a migrated component reveals an unanticipated production issue
  • Simple Visualforce pages with no real migration upside left as-is: Lightning Experience supports them natively, migrating the entire scope would have been cost with no measurable user benefit
  • Named Credentials deployed only on the REST integrations flagged as priority during the audit — secondary integrations remain on the old mechanism, migration documented as a next step