A web page may link to external resources such as stylesheets, or images (assets). With HTTP/1.1 the browser parses the HTML and for each link, it downloads the asset and eventually take an action on it: renders an image or evaluate JavaScript code. With HTTP/2 introduced an enhancement: the server can proactively “push” in parallel both the HTML payload and some assets to the browser. This workflow is allowed due to the HTTP/2 TCP connections are multiplexed. That means many communications can happen at the same time.
Unfortunately HTTP/2 adoption is still slow, so the IETF “backported” this workflow to HTTP/1.1 as well, by introducing the HTTP status 103 Early Hints. In this case the server sends one or more HTTP responses for a single request. The last one must be the traditional 200 OK
that returns the HTML of the page, whereas the first n
can include a special header Link
to tell the browser to fetch the asset ahead of time.
Setup
As first thing, you need Puma 3.11.0+
with Early-Hints enabled:
# Gemfile
gem "puma"
# config/puma.rb
early_hints true
Then from the project configuration, you can simply enable the feature:
# config/environment.rb
Hanami.configure do
# ...
early_hints true
end
As last step, you need a web server that supports HTTP/2 and Early Hints like h2o. When you’ll start the server and visit the page, javascripts and stylesheets will be pushed (see Assets helpers section).
Other web servers
As of today, only Puma supports Early Hints.
Assets helpers
In order to automatically push your assets, you have to use our assets helpers. But given to browser limitations (only up to 100 assets can be pushed), Hanami by default sends stylesheets and javascripts only.
Helper | Early Hints asset type | Pushed by default |
---|---|---|
javascript | :script | yes |
stylesheet | :style | yes |
favicon | :image | no |
image | :image | no |
video | :video | no |
audio | :audio | no |
asset_path | N/A | no |
asset_url | N/A | no |
You can opt-in/out the following types:
Javascripts
Pushed by default:
<%= javascript "application" %>
<%= javascript "https://somecdn.test/framework.js", "dashboard" %>
Opt-out:
<%= javascript "application", push: false %>
<%= javascript "https://somecdn.test/framework.css", "dashboard", push: false %>
Stylesheets
Pushed by default:
<%= stylesheet "application" %>
<%= stylesheet "https://somecdn.test/framework.css", "dashboard" %>
Opt-out:
<%= stylesheet "application", push: false %>
<%= stylesheet "https://somecdn.test/framework.css", "dashboard", push: false %>
Favicon
Opt-in:
<%= favicon "favicon.ico", push: :image %>
Image
Opt-in:
<%= image "avatar.png", push: :image %>
Audio
Opt-in:
<%= audio "song.ogg", push: true %>
Block syntax (pushes only song.ogg
):
<%=
audio do
text "Your browser does not support the audio tag"
source src: asset_path("song.ogg", push: :audio), type: "audio/ogg"
source src: asset_path("song.wav"), type: "audio/wav"
end
%>
Video
Opt-in:
<%= video "movie.mp4", push: true %>
Block syntax (pushes only movie.mp4
):
<%=
video do
text "Your browser does not support the video tag"
source src: asset_path("movie.mp4", push: :video), type: "video/mp4"
source src: asset_path("movie.ogg"), type: "video/ogg"
end
%>
Asset path
<%= asset_path "application.js", push: :script %>
Asset URL
<%= asset_url "https://somecdn.test/framework.js", push: :script %>
Demo project
If you’re looking for a full working example, please check this demo project.