D365 eCommerce : Do you get the picture ?

One of the most common frustrations in D365 Commerce sounds deceptively simple:

I updated a product (or image). Why isn’t it visible in eCommerce?

The short answer:
Because D365 Commerce is using async pattern for performance and caching.

The slightly longer (and more honest) answer:
Because your “simple update” now has to travel through a small obstacle course of jobs, sync processes, caches, and services—spread nicely across multiple systems—before it earns the right to appear on the storefront.

This post walks through what actually happens, what you need to do, and—most importantly—what you should realistically expect in terms of timing.

At a high level, updates flow through three layers:

  1. Authoring layer
    • D365 F&O (product data)
    • Site Builder (content, images, CMS)
  2. Distribution layer
    • Commerce Scale Unit (CSU)
    • CDX jobs
  3. Presentation layer
    • E-commerce frontend (cached, CDN-backed)

Step 1: Product updates in F&O

In F&O, you typically:

  • Update product name, attributes, price
  • Assign category

I’ll assume your category is already connected to an assortment (if not, that’s your first problem).

Also worth noting:
From a technical perspective, D365 Commerce mostly operates on products, not “released products” as you think about them in F&O. The data originates there—but Commerce consumes it differently. So “it looks correct in F&O” is not a guarantee of anything.

Before doing anything else, verify:

  • Product is released and assigned to a category
  • Product/category is included in an assortment
  • Assortment is linked to the correct eCommerce channel
  • Required attributes are populated

Then run your distribution jobs:

  • 1040 – Products
  • 1150 – Catalog
  • 1070 – Channel configuration (sometimes required)

Or, if you’re feeling efficient:

  • 9999 – Full sync (delta)

This pushes data from F&O → CSU.

Timing:

  • Manual run: ~1–3 minutes
  • Batch (recurring): typically 5–15 minutes

So no, it won’t show up “immediately”.


Step 2: Images and Site Builder (where things feel real-time… until they aren’t)

Next stop: Site Builder. I’ll assume you’re using Omnichannel media management—because anything else in 2026 is just self-inflicted pain.

Here you:

  • Upload images
  • Map images to products

And yes—this part is important:

You must publish both:

  • The media assets
  • The product-media mappings

Saving is not publishing. Preview is not live. We’ve all learned this the hard way.


Step 3: The hidden roundtrip (CMS → F&O)

Here’s the part most people don’t expect:

Before anything becomes visible in eCommerce, a batch job in F&O must run:

  • CMS to HQ omnichannel media sync

This job:

  • Pulls media mappings from CMS
  • Stores references in F&O

Yes—correct:
Your product images are effectively registered in F&O before eCommerce can use them.

Which also explains why:

  • You can (with extensions) surface eCommerce images inside F&O
  • And why missing this job results in… nothing showing up

Typical setup:

  • Runs every ~5 minutes as a batch job

Step 4: And back again (F&O → CSU… again)

At this point, you might reasonably think you’re done.

You are not.

Now that F&O has received the media mappings, you must send them back out again:

  • Run 1040 (or 9999)

Why?

Because eCommerce does not read directly from CMS.
It reads from CSU, which reads from F&O.

So the flow is:

CMS → F&O → CSU → eCommerce

Not exactly intuitive, but very consistent.


Step 5: Search indexing and cache (the final boss)

Running 1040 does two important things:

  1. Updates CSU with product + media references
  2. Triggers product search indexing

That indexing feeds Azure AI Search, which powers product search results.

So:

  • Direct product URLs may work before search does
  • Search results may lag behind

And then there’s caching.

From Microsoft’s own flow:

  • Product data can have up to 2-hour cache TTL
  • Azure AI Search indexing can take significant time

Real-world observation:

  • ~10,000 products → indexing can take 1+ hour per channel

Yes, per channel.


The important takeaway

After you:

  • Upload and publish media
  • Sync CMS → F&O
  • Run CDX (again)
  • Wait for search indexing
  • And let caching expire

…then your product might show up exactly where you expected it.


Final reality check

D365 Commerce is eventually consistent.

Not “slightly delayed.”
Not “near real-time.”

Eventually.

If you treat it like a real-time system, you will spend your time:

  • re-running jobs
  • second-guessing configurations
  • and refreshing the browser aggressively

Instead of just understanding where in the pipeline you are.

That’s the difference between guessing—and knowing exactly why nothing is showing up (yet).

So the final conclusion is that if you have done everything correctly; Check your eCommerce site again tomorrow.

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 🙂