Loading...

Canvas app performance: delegation, caching, and lazy patterns

A Canvas app that loads in under two seconds is not an accident. It is the result of specific decisions about when to query, what to cache, and what to defer. Here are the patterns we apply when a Canvas app feels sluggish.

canvas-app-performance-patterns

A Canvas app we inherited took 14 seconds to load. The screens were responsive once visible, but the initial splash sat blank. Users hated it. After three days of work, the same app loaded in 2.1 seconds. The fix was not one clever trick - it was four patterns applied in sequence. Here they are, in the order we apply them.

Step 1: measure, don't guess

Before optimizing anything, run the Canvas app's built-in Monitor tool against a realistic session. Monitor records every operation - data source call, formula execution, screen change - with duration.

Sort by duration descending. The top 10 operations are almost always where 80% of the time is spent. Without this, you can spend a day optimizing formulas that were never the bottleneck.

Typical findings on a slow app:

  • App.OnStart runs 3 sequential data source queries, each taking 1-2 seconds → 4-6 seconds before any screen is visible.
  • Screen.OnVisible on the default screen runs another query → additional 1-2 seconds before the screen is interactive.
  • Formulas depending on LookUp against a 5000-row table re-execute on every control refresh.

Each of these has a pattern-fix.

Pattern 1: don't wait on App.OnStart

The common mistake: loading "everything the app needs" in App.OnStart before any screen renders. Every query in OnStart is time the user stares at a splash screen.

The fix: load only what the first screen needs. Later screens load their own data on OnVisible, cached in a collection for subsequent visits.

The app is interactive in 200-500ms. Data for each screen loads as the user navigates to it. Subsequent visits are instant because the collection is cached.

Trade-off: on slow networks, the first visit to each screen has a perceptible pause. For most business apps this is acceptable; for latency-critical scenarios, preload in the background using Concurrent().

Pattern 2: Concurrent() for independent queries

If App.OnStart genuinely needs multiple queries before the app can render (rare), run them in parallel instead of sequentially.

The Concurrent function runs all its arguments in parallel. Total duration is the slowest single query, not the sum. For three 1-second queries, you save 2 seconds.

Caveat: queries that depend on each other cannot be run concurrently. varUserSettings that uses the result of varCategories must stay sequential (or be split into two phases).

Pattern 3: cache lookups in collections

A formula like LookUp(Users, ID = item.UserID).Name inside a gallery's label re-executes on every render. For a gallery of 50 items, that's 50 lookups per paint. If the gallery re-paints on scroll, thousands of lookups happen.

Fix: load the lookup table once into a collection, index into the collection from the formula.

LookUp against an in-memory collection is O(n) but the n is small and the operation is microseconds. Against a Dataverse table, it is a server round trip per call.

For repeated lookups on the same key, build a hash-like collection:

The reduction in columns keeps the collection small; the in-memory lookup is fast.

Pattern 4: lazy loading galleries

A gallery that shows 2000 items scrolls slowly because every item's controls re-render on scroll. The Canvas runtime has virtualized rendering but only when the gallery's data source supports paging.

Fix: limit the gallery to a paged subset.

With pageSize = 50, the gallery holds at most 50 rendered items regardless of the underlying data size. Navigation controls let the user page through.

For "infinite scroll" UX, trigger Collect() to append more items as the user approaches the end - but this only works for small growth steps before the gallery itself becomes a bottleneck again.

Pattern 5: screen transition timing

Screen transitions have a built-in animation. On slow devices, the animation itself adds 300-500ms to perceived navigation.

Settings → Advanced settings → "Enable transitions" can be turned off. For business apps where speed matters more than polish, we disable transitions globally.

For specific transitions where animation aids UX (a modal sliding in), override just that screen to keep transitions on.

Pattern 6: reduce the visual complexity

Canvas apps render all controls on the current screen, even those off-screen. A screen with 200 controls (nested galleries, headers, footers, hidden panels) is slower than a screen with 50.

Fix:

  • Minimize hidden-by-default panels (use separate screens instead).
  • Combine repetitive controls into a gallery where possible (instead of 10 labels, one gallery of 10 items).
  • Remove controls that only serve design purposes and can be replaced with background images.

This is visual diet; it helps on low-end mobile devices more than on desktop.

The measurement pipeline we run

Every time a performance-sensitive Canvas app gets a significant change, we re-run Monitor with a standardized script:

  1. Launch the app.
  2. Wait for first screen to be interactive.
  3. Navigate to three key screens in sequence.
  4. Perform a sample action on each (open a form, filter a gallery).
  5. Record Monitor output.

Compare the key durations to the previous baseline. Any regression triggers investigation before merge.

The baselines live in a spreadsheet the team updates. Over two years, this has caught dozens of regressions before they hit users.

The inherited-app checklist

When we inherit a slow Canvas app, our first pass is:

  1. Measure with Monitor.
  2. Check App.OnStart for sequential queries - move them to Concurrent or to per-screen OnVisible.
  3. Check formulas inside galleries for repeated LookUps - cache into collections.
  4. Check gallery sizes - paginate if over 500 rows.
  5. Disable transitions if the team accepts it.

Three days of work, typically. For the 14-second app mentioned earlier, the fix was:

  • Two of three App.OnStart queries moved to per-screen → saved 3 seconds.
  • Gallery LookUp against full Users table cached → saved 1.5 seconds per gallery paint × several paints at startup = 4+ seconds.
  • Transitions disabled → saved ~400ms per navigation.
  • Remaining optimizations: minor formula tweaks.

The app now loads in 2.1 seconds and feels snappy. Users stopped complaining. And our team finally understands why Canvas apps sometimes feel slow even when the Power Fx formulas "look fine."

Contact Us Now

Share Your Story

We build trust by delivering what we promise – the first time and every time!

We'd love to hear your vision. Our IT experts will reach out to you during business hours to discuss making it happen.

WHY CHOOSE US

"Collaborate, Elevate, Celebrate where Associates - Create Project Excellence"

SapotaCorp beyond the IT industry standard, we are

  • Certificated
  • Assured quality
  • Extra maintenance

Tell us about your project

close