D365 and the performance of app.css

Each time you load a D365 form som scratch, and you take a view in F12, you will see that there are a lot of calls happening, but one of them, that often stands out are App.Css.

app.css may look like “just a stylesheet,” but it’s a foundational part of the Dynamics 365 F&O web client. Because it is render-blocking and downloaded at the start of each user session, any issue with its size, compression, or caching has a direct impact on the speed and responsiveness of the entire system.

At live customers where we see download time of vary from 3s to 12s, and it’s size is approx. 15.9 MB. My experience is that if this file is downloaded slow, then users complain about performance issues, and that the F&O feels “sluggish”.

You can try it out on your own environment by going to :
[Your F&O URL]/WebContent/ApplicationSuite/less/21/0/app.css

I often see user using favorites or pressing F5. This ALWAYS downloads the file. But if I navigate nicely through menus and forms, it uses the the existing downloaded APP.CSS file. I don’t know why there are not better caching on this file, because it it directly related to the user experienced performance.

I did find the following resolved issue on the matter, but I cannot see that it works :

But this fix does not seam to work when doing a hard reload, browsers bypass the cache and pull the file again.

The request header is :
Cache-Control: no-cache
Pragma: no-cache
Accept-Encoding: gzip, deflate, br, zstd

The response header is:

Cache-Control: must-revalidate, private
Content-Length: 15864379 (≈ 15.8 MB)
Date: Sun, 23 Nov 2025 11:12:13 GMT
Expires: Sat, 22 Nov 2025 11:12:14 GMT
Last-Modified: Mon, 06 Oct 2025 14:47:48 GMT


Some questions I have is:


No compression. Why ?

  • With gzip/Brotli, you’d probably be in the 1–3 MB range instead of 15.8 MB.
  • Browser advertises Accept-Encoding: gzip, deflate, br, zstd.
  • Server returns no Content-Encoding → raw 15.8 MB.

Cache semantics are conservative.

  • Cache-Control: must-revalidate, private
  • Expires is already in the past
  • This is cacheable by the browser, but:
    • It will tend to revalidate more often.
    • It will never be shared via proxies/CDNs.

I asked ChatGPT to explain what we could experience if we had some serious improvements on how app.css is handed :

The current implementation of app.css in Dynamics 365 Finance & Operations represents a significant and unnecessary performance bottleneck. In multiple production environments, this single stylesheet is delivered as a 15.8 MB monolithic file, served without HTTP compression, and marked with conservative caching headers that force frequent revalidation. This design means every cold session—and far too many warm sessions—incurs several seconds of render-blocking download time before the UI can even appear. This contradicts modern web performance standards and is fundamentally out of alignment with best practices used across Microsoft’s own cloud products.

Even worse: the bundle includes CSS for dozens of feature areas the user will never open, yet all that styling is shipped up front in a single blocking request. A sensible architecture would split critical UI styling into a small, cache-friendly core bundle and load feature/workspace CSS asynchronously. Combined with proper Cache-Control headers and gzip/Brotli compression—both trivial to implement—first-paint latency would drop from multiple seconds to well under a second, and warm loads would be effectively instantaneous.

Put bluntly: no enterprise web application in 2025 should ship a 15 MB uncompressed render-blocking CSS file, and F&O is long overdue for a cleanup here. Microsoft can dramatically improve perceived performance across all customers by modernizing static asset delivery for these core UI bundles.

To put the impact into perspective: today a 15.8 MB uncompressed app.css over a typical 15–30 Mbit/s corporate connection costs roughly 4–9 seconds of pure transfer time on every cold load — and that’s before the browser even starts rendering the UI. The same stylesheet, if split and compressed down to ~2 MB of critical CSS, would load in well under a second on the same line speed. With proper client caching on top, most users would pay this cost once per update, not once per session. In other words, a trivial change in how static assets are packaged and cached would turn “wait 5–10 seconds for the client to wake up” into “page is ready almost immediately” for every F&O customer on the planet. “

Hmmm…. Chatty agrees with me 🙂 This should be fixed.

Here is the “idea” for votes : https://experience.dynamics.com/ideas/idea/?ideaid=de4f0ff0-dac9-f011-ad8e-7c1e52cc5c16

D365 eCommerce : Let’s talk about WEBP

In the architectural overview of Dynamics 365 eCommerce we can see that there are a few central components dealing with digital assets like images, videos and documents.

A central component is image resizer service that automatically adjusts the size and quality of images according to the device and context of the user. This helps improve performance and user experience. In essence it is performing a LOT of caching of resized images.

The format is :
&w=WIDTH_NUMBER
&h=HEIGHT_NUMBER
&m=MODE_NUMBER
&q=QUALITY
&f=IMAGE_FORMAT

WIDTH_NUMBER and HEIGHT_NUMBER specify the width and height values in pixels (0–3000), and MODE_NUMBER specifies the image resizer mode to use.

The image resizer is quite compute intensive, so the end result is heavily cached end to end.  It also seems that the resizer is a shared service per geo.

To better understand the image resizer I have performed some light weight test, by using the F12 developer tools, and here is a tip to test and find what parameters does fit your requirement.

Let’s take the Microsoft demo site : https://www.adventure-works.com/duonovi-pro-men-s-coat/68719519871.p

When I press F12 and filter on images, I can see the URL to both the CMS and the image resizer

If I right click on one of the requests I can edit and resend:

I can then test the performance and timing loading of the pictures, by changing the w, h,q, f and m:

I can also just add additional parameters like I have done above, by adding “test=Test1”, as this will bypass the server side caching and allows me to test the performance of the resizer.  By clicking “send” I can then get a quite good ide of how long a “cold cache” image resize would behave.

So to save you the time, I did a few tests so that you could see the difference in imnage sizew and resonse time. (The server here is in the US, while I’m in Norway)

ScenarioImage sizeTime
Fetching the “raw” image without cache (png)360 kb921 ms
Fetching the “raw” image with cache (png)360 kb141 ms
Testing with PNG uncached
&w=0&h=772&q=80&m=6&f=png
526 kb970 ms
Testing with PNG cached
&w=0&h=772&q=80&m=6&f=png
526 kb135 ms
Testing with jpg uncached
&w=0&h=772&q=80&m=6&f=jpg
66 kb527 ms
Testing with jpg cached
&w=0&h=772&q=80&m=6&f=jpg
66 kb88 ms
Testing with webp uncached
&w=0&h=772&q=80&m=6&f=webp
18 kb782 ms
Testing with webp cached
&w=0&h=772&q=80&m=6&f=webp
18 kb45 ms

So my unofficial conclusion is that fetching images uncached takes a long time.  The reason is that the resizer uses a lot of time, and I also see the larger the raw file is, the more time is uses to create the cached versions.

But when the image is cached, the webp format is superior and also results in the fastest download time and image size.  As far as I see in the site builder, the current modules are fetching images in jpg format, and this gives a OK cached performance.

What we have done in our projects is to switch to the webp url for better performance on loading images.  I specially see that on the PDP (Product Details Page), when looking at the zoomed image, the f= parameter is not present.  And if you have a large PNG raw, then the timing of resizing and fetching the image can be many seconds:  Like https://images-us-prod.cms.commerce.dynamics.com/cms/api/stpmsksxpr/imageFileData/search?fileName=/Products%2F61100_000_002.png&fallback=/Products/61100_000_002.png&m=6&q=80

I also think it is a good idea to land on a standardized raw image size, and my recommendation is W=1280 and H=1972. When fetching the picture from the resizer, also try out q= in the range of 50-80 to balance between picture size and quality. On a server cached scenario you should look for a “waiting for server response” in the range of 40-60ms and content download around the same.

Conclusion

When working with pictures in Dynamics 365 eCommerce, be aware of the format and size in the URL to get good performance.  Start looking into if you want to try out the webp format to get even better performance. (Not supported in std modules yet, but the resizer seams to support it and will require that you clone a few modules to add support for this).  Also read the following page to better understand possibilities : https://learn.microsoft.com/en-us/dynamics365/commerce/e-commerce-extensibility/image-component

Happy DAX’ing 🙂