Visualforce ne disparaît pas du jour au lendemain, mais les Lightning Web Components sont le présent et le futur du développement UI Salesforce. Migrer de façon incrémentale — un composant à la fois — est l'approche réaliste pour la plupart des orgs.
Quand migrer (et quand ne pas le faire)
Bons candidats à la migration :
- Pages à fort trafic qui bénéficieraient des performances LWC
- Pages dont les utilisateurs se plaignent de la lenteur (le view state VF est un coupable courant)
- Nouvelles fonctionnalités que vous construisez à côté de pages VF existantes
Gardez en Visualforce :
- Génération de PDF (VF génère nativement des PDF — LWC non)
- Templates d'email utilisant
<messaging:emailTemplate> - Grilles d'édition inline complexes nécessitant une refonte importante
- Pages qui fonctionnent et que personne ne touche — ne migrez pas pour migrer
Correspondance des concepts
| Visualforce | Équivalent LWC |
|-------------|----------------|
| <apex:page> | Fichiers html + js du composant |
| <apex:form> + <apex:commandButton> | <form> + Server Action ou @wire |
| <apex:inputField> | <lightning-input-field> |
| <apex:outputField> | <lightning-output-field> |
| <apex:pageBlock> | <lightning-card> |
| <apex:dataTable> | <lightning-datatable> |
| <apex:repeat> | Directive for:each |
| Contrôleur standard | Wire adapters lightning/uiRecordApi |
| Contrôleur personnalisé | Classe Apex appelée avec @wire ou callApex |
| $User.Id, $Label | @salesforce/user/Id, @salesforce/label/c.Mon_Label |
Migration d'affichage de champs simple
Visualforce :
<apex:page standardController="Account">
<apex:pageBlock title="Détails du compte">
<apex:pageBlockSection>
<apex:outputField value="{!Account.Name}"/>
<apex:outputField value="{!Account.Phone}"/>
<apex:outputField value="{!Account.Industry}"/>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:page>Équivalent LWC :
<!-- detailsCompte.html -->
<template>
<lightning-card title="Détails du compte">
<div class="slds-p-around_medium">
<lightning-record-view-form record-id={recordId} object-api-name="Account">
<lightning-output-field field-name="Name"></lightning-output-field>
<lightning-output-field field-name="Phone"></lightning-output-field>
<lightning-output-field field-name="Industry"></lightning-output-field>
</lightning-record-view-form>
</div>
</lightning-card>
</template>// detailsCompte.js
import { LightningElement, api } from 'lwc';
export default class DetailsCompte extends LightningElement {
@api recordId;
}Migration d'un formulaire avec logique personnalisée
LWC avec Server Action :
// formulaireCompte.js
import { LightningElement } from 'lwc';
import { NavigationMixin } from 'lightning/navigation';
import creerCompte from '@salesforce/apex/AccountFormController.creerCompte';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
export default class FormulaireCompte extends NavigationMixin(LightningElement) {
nom = '';
telephone = '';
handleNomChange(event) {
this.nom = event.target.value;
}
async handleSave() {
if (!this.nom) {
this.dispatchEvent(new ShowToastEvent({
title: 'Erreur de validation',
message: 'Le nom est obligatoire',
variant: 'error',
}));
return;
}
try {
const accountId = await creerCompte({ nom: this.nom, telephone: this.telephone });
this[NavigationMixin.Navigate]({
type: 'standard__recordPage',
attributes: { recordId: accountId, actionName: 'view' },
});
} catch (error) {
this.dispatchEvent(new ShowToastEvent({
title: 'Erreur',
message: error.body?.message ?? 'Erreur inconnue',
variant: 'error',
}));
}
}
}Remplacer <apex:repeat> et les tableaux dynamiques
Visualforce :
<apex:repeat value="{!contacts}" var="c">
<div>{!c.Name} — {!c.Email}</div>
</apex:repeat>LWC :
<template for:each={contacts} for:item="contact">
<div key={contact.Id}>
{contact.Name} — {contact.Email}
</div>
</template>Pour les tableaux éditables, utilisez <lightning-datatable> :
colonnes = [
{ label: 'Nom', fieldName: 'Name', type: 'text', editable: true },
{ label: 'Email', fieldName: 'Email', type: 'email' },
{ label: 'Téléphone', fieldName: 'Phone', type: 'phone' },
];Stratégie de migration
- Identifier la complexité de la page — simple (< 1 jour), moyenne (1–3 jours), complexe (1+ semaine)
- Créer le LWC à côté de la page VF — ne pas supprimer VF avant que le LWC soit validé
- Utiliser un feature flag — Custom Setting
LWC_Active__cpour basculer entre VF et LWC par utilisateur - Valider avec les utilisateurs d'abord — déployer LWC sur un sous-ensemble d'utilisateurs avant le déploiement complet
- Supprimer la page VF — seulement après 2–4 semaines d'utilisation LWC réussie
Pièges courants
- View state → pas de view state : LWC n'a pas de concept de view state — utilisez
@wirepour les données, les propriétés régulières pour l'état local - Redirections de page : VF utilise
PageReference— LWC utiliseNavigationMixin apex:messages: les messages d'erreur VF utilisentApexPages.addMessage— LWC utiliseShowToastEvent- CSS inline dans VF : LWC impose le Lightning Design System — évitez les CSS bruts dans la portée du composant