You’ve spotted the problem in your last paragraph.
In terms of JavaScript execution putting defer
scripts in head or at bottom has little difference (though it’s different for async
scripts). However do be aware that defer scripts execute in order so any other defer script in middle of the page will execute after defer scripts in the head and before defer scripts at the bottom of the page. Usually when using defer you don’t care about the execution time but if there are dependencies then ordering can be important (i.e. loading jQuery before a script that needs jQuery) and defer
honours ordering (where as async
does not).
But yes downloads will cause other elements to suffer. Yes the spec recommends two connections are most, but in reality most browsers use 6 connections for HTTP/1.1. This means if your low-priority, defer
scripts are using one of those there’s only 5 connections left for everything else on that page. It is likely there are higher priority resources on the page than defer
scripts so this is wasteful.
Browsers will scan ahead in the HTML to get a list of all the resources, and assign a priority to each resource (which will be low for defer
scripts) but at the same time the browser will start taking items off the top of this prioritised queue and a low-priority defer
script in the head may be started on before the other resources are seen. And because of this “preload scanner”, any benefit of putting defer
scripts in the head is minimal. The browser will still see it and request it when it can. Yes it’ll take slightly longer to see it when at the bottom (particularly for large pages that might take a while to fully download) but it’ll still see it as soon as the HTML is downloaded in its entirety, even if it’s still processing scripts and other resources on the page so hasn’t got to the end of the HTML.
In an HTTP/2 world, where there is not 6-connection limit you may think this doesn’t matter as much, but it often does. Partly because the connection limit is not completely unlimited (100 or 128 parallel streams are common limits on HTTP/2 servers) but more so because bandwidth is not completely unlimited and so download contention is a real thing. HTTP/2 has the concept of prioritisation so in theory should be able to handle this well, but in practice HTTP/2 prioritisation is often poorly implemented on servers and surrounding infrastructure that you can’t depend on this.
Patrick Meenan has an awesome talk about how browsers load resources, particularly touching on the HTTP/2 issues. Well worth a watch if interested in this sort of thing.
So, in summary, I would still keep defer scripts to the bottom of the page for now.