In Chrome, a preload
tag ends up pushing the priority of a resource ahead of any other, non-preloaded resource. This is detrimental to the FMP time, because CSS ends up prioritized after the preloaded JS.
Note the special treatment of core-bundle.js
, getting its first byte before any other asset
Note: The timeline above shows all resources sharing bandwidth, but devtools is lying. Each resource is getting downloaded exclusively
Asset order in the DOM
I verified through chrome://net-export/
that Chrome is signaling to the server that core-bundle.js
should be the first resource sent after the initial HTML Document. Below are the streams for the first few critical assets, in the order the streams were created:
Resource |
Stream Weight |
Stream ID |
Parent Stream ID |
Exclusive Bit |
Initial HTML Document |
256 |
1 |
0 |
TRUE |
core-bundle.js |
220 |
3 |
0 |
TRUE |
calendar.css |
256 |
5 |
0 |
TRUE |
styles-m.css |
256 |
7 |
5 |
TRUE |
styles-l.css |
256 |
9 |
7 |
TRUE |
require.js |
220 |
11 |
3 |
TRUE |
Looking through HTTP2_SESSION_RECV_DATA
events in the net export log, you can see the real order the server flushed the responses in below. Note that Chrome used the exclusive
bit on every stream, so assets are given 100% bandwidth and downloaded in the order specified between stream weight and priorities.
Real Download Order:
- Initial HTML Document
- core-bundle.js
- styles-l.css
- styles-m.css
- calendar.css
- require.js
core-bundle.js
and calendar.css
are both given a parent stream ID of 0
. According to the H2 spec, when the exclusive
bit is used and multiple dependencies are added to a parent stream, it should create this tree
parent stream > first dependency > second dependency
Because of this, core-bundle.js
gets downloaded before any of the css on the page, even though the CSS has a higher stream weight. It seems like usage of the exclusive bit on every stream causes stream weights to be ignored, based on my reading of the spec. In particular, these 2 parts:
An exclusive flag allows for the insertion of a new level of
dependencies. The exclusive flag causes the stream to become the
sole dependency of its parent stream, causing other dependencies to
become dependent on the exclusive stream
https://tools.ietf.org/html/rfc7540#section-5.3.1
Streams with the same parent SHOULD be allocated resources
proportionally based on their weight
https://tools.ietf.org/html/rfc7540#section-5.3.2
Since the exclusive
bit on every stream means no 2 resources will share a parent, the stream weight is never used.
IMO, Chrome's handling of H2 priorities here is wrong/buggy. If Chrome knows it's going to assign a higher weight to calendar.css
than to core-bundle.js
, it should be making calendar.css
a dependency of parent 0 before core-bundle.js
, which would result in the JS being prioritized immediately after the CSS, fixing the FMP timing.