3.5 Optimizing Canvas and Model-Driven App Performance
Key Takeaways
- The App Checker's Performance tab flags large images, excessive controls, non-delegable queries, and heavy OnStart formulas.
- Effective pre-loading means minimal App.OnStart plus screen-scoped OnVisible loads, not one large synchronous pull of an entire table.
- Delegable, filtered queries combined with Concurrent() reduce both server load and canvas app load time simultaneously.
- Model-driven form performance depends on minimizing visible fields and tabs and disabling default sub-grid data loading.
- Model-driven view performance depends on limiting displayed columns and filtering or sorting on indexed columns.
Measuring Before Optimizing
Power Apps Studio includes a built-in App Checker, with a Performance tab that flags specific, actionable issues — large images, excessive controls on a screen, non-delegable queries, and heavy OnStart or OnVisible formulas — rather than leaving developers to guess. The blueprint's "optimize canvas app performance" sub-topic (3e) centers on two named techniques — pre-loading data and query delegation — plus a broader set of practices that reduce the amount of work the app does on load and per interaction.
Pre-Loading Data the Right Way
"Pre-loading" doesn't mean pulling every table into a giant App.OnStart collection — that pattern is a common anti-pattern that increases startup time because the app blocks on a large synchronous data pull before the first screen even renders. The recommended pattern instead:
- Keep
App.OnStartminimal — only global settings, user context, or small lookup tables genuinely needed everywhere. - Load screen-specific data in that screen's
OnVisible, not centrally, so the cost is paid only when a user actually visits the screen. - Where data really should be cached client-side (for speed or offline use), use
ClearCollectagainst a delegable, filtered query rather than the full table, and wrap independent loads inConcurrent()so they run in parallel instead of one after another. - Use
Concurrent()broadly for any set ofOnStartorOnVisiblecalls that don't depend on each other's results — it's one of the highest-value fixes the App Checker's Performance tab typically surfaces.
Delegation as a Performance Lever
Delegation is also fundamentally a performance concern: a delegable Filter or Sort pushes work to the server and returns only the rows needed, while a non-delegable formula silently caps results at the delegation limit and does the filtering client-side after retrieving up to that many rows — slower and less correct at scale. Raising the delegation limit setting is not a fix; it increases the number of rows pulled locally rather than solving the underlying non-delegable expression, trading a larger payload for the same fundamental bottleneck.
Reducing Control Count and Screen Complexity
Beyond data, rendering cost scales with the number of controls and rules a screen must evaluate:
| Technique | Effect |
|---|---|
| Minimize total visible controls per screen | Fewer property formulas to re-evaluate on every state change |
| Avoid nested galleries where a flat structure works | Reduces per-row control multiplication |
| Hide, don't duplicate, near-identical screens | Fewer app-wide OnVisible and data triggers |
| Compress or resize images before import; prefer SVG for icons | Smaller app payload, faster load |
| Limit the number of distinct data sources referenced by one screen | Fewer connection handshakes on screen load |
Formulas referencing Self or Parent excessively, or deeply nested If/Switch chains recalculated on every keystroke, add up the same way — the Performance tab flags screens whose combined control and formula cost crosses platform-recommended thresholds.
Model-Driven Form Performance
Model-driven apps optimize differently because forms are metadata-driven rather than formula-driven. Key levers for "optimize model-driven app performance (forms and views)" (3f):
- Minimize fields and tabs on the main form — every visible field, especially those bound to formatted or calculated columns or lookups, adds retrieval and rendering cost; move rarely-used fields to a secondary tab that loads only when clicked.
- Turn off default data loading for sub-grids and related-record tabs, so they lazy-load only when a user actually expands them, instead of running a query for every related entity on every form open.
- Limit business rules and synchronous JavaScript on form load — each adds sequential client-side execution time before the form is interactive; asynchronous patterns and event-scoped handlers that only fire on the field or save events they actually need outperform broad on-load scripts that touch every field.
- Use quick view forms sparingly — each one issues its own retrieve against the related record.
Model-Driven View Performance
Views are essentially FetchXML queries rendered as grids, so view performance follows normal query-optimization logic:
- Limit displayed columns to what users need; every column adds to the query and render cost, especially formatted lookup or option-set columns.
- Filter and sort on indexed columns. Dataverse automatically indexes primary keys and some system columns; sorting or filtering large tables on non-indexed custom columns is slow at scale, and heavily-filtered custom columns are good candidates for requesting a custom index.
- Avoid unnecessary related-entity columns in a view, since each pulls from a joined table.
- Cap default record counts sensibly and rely on server-side paging rather than large page sizes that pull excess rows the user won't scroll to.
Across both app types, the underlying principle is the same one the blueprint tests repeatedly: retrieve only what's needed, only when it's needed, and let the server, not the client, do filtering and sorting whenever delegation or indexing makes that possible.
A canvas app's App.OnStart collects an entire 40,000-row Orders table into a local collection before the first screen renders. What is the most accurate assessment of this pattern?
A model-driven app's main form is slow to open because every sub-grid and related-record tab queries its data immediately on load. What is the recommended fix?