mirror of
https://github.com/symfony/symfony-docs.git
synced 2026-03-23 16:22:10 +01:00
217 lines
8.8 KiB
ReStructuredText
217 lines
8.8 KiB
ReStructuredText
Asset Preloading and Resource Hints with HTTP/2 and WebLink
|
|
===========================================================
|
|
|
|
Symfony provides native support (via the `WebLink`_ component)
|
|
for managing ``Link`` HTTP headers, which are the key to improve the application
|
|
performance when using HTTP/2 and preloading capabilities of modern web browsers.
|
|
|
|
``Link`` headers are used in `HTTP/2 Server Push`_ and W3C's `Resource Hints`_
|
|
to push resources (e.g. CSS and JavaScript files) to clients before they even
|
|
know that they need them. WebLink also enables other optimizations that work
|
|
with HTTP 1.x:
|
|
|
|
* Asking the browser to fetch or to render another web page in the background;
|
|
* Making early DNS lookups, TCP handshakes or TLS negotiations.
|
|
|
|
Something important to consider is that all these HTTP/2 features require a
|
|
secure HTTPS connection, even when working on your local machine. The main web
|
|
servers (Apache, nginx, Caddy, etc.) support this, but you can also use the
|
|
`Docker installer and runtime for Symfony`_ created by Kévin Dunglas, from the
|
|
Symfony community.
|
|
|
|
Installation
|
|
------------
|
|
|
|
In applications using :ref:`Symfony Flex <symfony-flex>`, run the following command
|
|
to install the WebLink feature before using it:
|
|
|
|
.. code-block:: terminal
|
|
|
|
$ composer require symfony/web-link
|
|
|
|
Preloading Assets
|
|
-----------------
|
|
|
|
Imagine that your application includes a web page like this:
|
|
|
|
.. code-block:: html
|
|
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>My Application</title>
|
|
<link rel="stylesheet" href="/app.css">
|
|
</head>
|
|
<body>
|
|
<main role="main" class="container">
|
|
<!-- ... -->
|
|
</main>
|
|
</body>
|
|
</html>
|
|
|
|
In a traditional HTTP workflow, when this page is loaded, browsers make one
|
|
request for the HTML document and another for the linked CSS file. However,
|
|
with HTTP/2, your application can send the CSS file's contents to the browser
|
|
before it requests them.
|
|
|
|
To achieve this, update your template to use the ``preload()`` Twig function
|
|
provided by WebLink. Note that the `"as" attribute`_ is required, as browsers use
|
|
it to prioritize resources correctly and comply with the content security policy:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
<head>
|
|
<!-- ... -->
|
|
{# note that you must add two <link> tags per asset:
|
|
one to link to it and the other one to tell the browser to preload it #}
|
|
<link rel="preload" href="{{ preload('/app.css', {as: 'style'}) }}" as="style">
|
|
<link rel="stylesheet" href="/app.css">
|
|
</head>
|
|
|
|
If you reload the page, the perceived performance will improve because the
|
|
server responded with both the HTML page and the CSS file when the browser only
|
|
requested the HTML page.
|
|
|
|
.. tip::
|
|
|
|
When using the :doc:`AssetMapper component </frontend/asset_mapper>` to link
|
|
to assets (e.g. ``importmap('app')``), there's no need to add the ``<link rel="preload">``
|
|
tag. The ``importmap()`` Twig function automatically adds the ``Link`` HTTP
|
|
header for you when the WebLink component is available.
|
|
|
|
.. note::
|
|
|
|
You can preload an asset by wrapping it with the ``preload()`` function:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
<head>
|
|
<!-- ... -->
|
|
<link rel="preload" href="{{ preload(asset('build/app.css')) }}" as="style">
|
|
<!-- ... -->
|
|
</head>
|
|
|
|
Additionally, according to `the Priority Hints specification`_, you can signal
|
|
the priority of the resource to download using the ``importance`` attribute:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
<head>
|
|
<!-- ... -->
|
|
<link rel="preload" href="{{ preload('/app.css', {as: 'style', importance: 'low'}) }}" as="style">
|
|
<!-- ... -->
|
|
</head>
|
|
|
|
How does it work?
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
The WebLink component manages the ``Link`` HTTP headers added to the response.
|
|
When using the ``preload()`` function in the previous example, the following
|
|
header was added to the response: ``Link </app.css>; rel="preload"; as="style"``
|
|
According to `the Preload specification`_, when an HTTP/2 server detects that
|
|
the original (HTTP 1.x) response contains this HTTP header, it will
|
|
automatically trigger a push for the related file in the same HTTP/2 connection.
|
|
|
|
Popular proxy services and CDNs including `Cloudflare`_, `Fastly`_ and `Akamai`_
|
|
also leverage this feature. It means that you can push resources to clients and
|
|
improve performance of your applications in production right now.
|
|
|
|
If you want to prevent the push but let the browser preload the resource by
|
|
issuing an early separate HTTP request, use the ``nopush`` option:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
<head>
|
|
<!-- ... -->
|
|
<link rel="preload" href="{{ preload('/app.css', {as: 'style', nopush: true}) }}" as="style">
|
|
<!-- ... -->
|
|
</head>
|
|
|
|
Resource Hints
|
|
--------------
|
|
|
|
`Resource Hints`_ are used by applications to help browsers when deciding which
|
|
resources should be downloaded, preprocessed or connected to first.
|
|
|
|
The WebLink component provides the following Twig functions to send those hints:
|
|
|
|
* ``dns_prefetch()``: "indicates an origin (e.g. ``https://foo.cloudfront.net``)
|
|
that will be used to fetch required resources, and that the user agent should
|
|
resolve as early as possible".
|
|
* ``preconnect()``: "indicates an origin (e.g. ``https://www.google-analytics.com``)
|
|
that will be used to fetch required resources. Initiating an early connection,
|
|
which includes the DNS lookup, TCP handshake, and optional TLS negotiation, allows
|
|
the user agent to mask the high latency costs of establishing a connection".
|
|
* ``prefetch()``: "identifies a resource that might be required by the next
|
|
navigation, and that the user agent *should* fetch, such that the user agent
|
|
can deliver a faster response once the resource is requested in the future".
|
|
* ``prerender()``: " **deprecated** and superseded by the `Speculation Rules API`_,
|
|
identifies a resource that might be required by the next
|
|
navigation, and that the user agent *should* fetch and execute, such that the
|
|
user agent can deliver a faster response once the resource is requested later".
|
|
|
|
The component also supports sending HTTP links not related to performance and
|
|
any link implementing the `PSR-13`_ standard. For instance, any
|
|
`link defined in the HTML specification`_:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
<head>
|
|
<!-- ... -->
|
|
<link rel="alternate" href="{{ link('/index.jsonld', 'alternate') }}">
|
|
<link rel="preload" href="{{ preload('/app.css', {as: 'style', nopush: true}) }}" as="style">
|
|
<!-- ... -->
|
|
</head>
|
|
|
|
The previous snippet will result in this HTTP header being sent to the client:
|
|
``Link: </index.jsonld>; rel="alternate",</app.css>; rel="preload"; nopush``
|
|
|
|
You can also add links to the HTTP response directly from controllers and services::
|
|
|
|
// src/Controller/BlogController.php
|
|
namespace App\Controller;
|
|
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use Symfony\Component\WebLink\GenericLinkProvider;
|
|
use Symfony\Component\WebLink\Link;
|
|
|
|
class BlogController extends AbstractController
|
|
{
|
|
public function index(Request $request): Response
|
|
{
|
|
// using the addLink() shortcut provided by AbstractController
|
|
$this->addLink($request, (new Link('preload', '/app.css'))->withAttribute('as', 'style'));
|
|
|
|
// alternative if you don't want to use the addLink() shortcut
|
|
$linkProvider = $request->attributes->get('_links', new GenericLinkProvider());
|
|
$request->attributes->set('_links', $linkProvider->withLink(
|
|
(new Link('preload', '/app.css'))->withAttribute('as', 'style')
|
|
));
|
|
|
|
return $this->render('...');
|
|
}
|
|
}
|
|
|
|
.. tip::
|
|
|
|
The possible values of link relations (``'preload'``, ``'preconnect'``, etc.)
|
|
are also defined as constants in the :class:`Symfony\\Component\\WebLink\\Link`
|
|
class (e.g. ``Link::REL_PRELOAD``, ``Link::REL_PRECONNECT``, etc.).
|
|
|
|
.. _`WebLink`: https://github.com/symfony/web-link
|
|
.. _`HTTP/2 Server Push`: https://tools.ietf.org/html/rfc7540#section-8.2
|
|
.. _`Resource Hints`: https://www.w3.org/TR/resource-hints/
|
|
.. _`Docker installer and runtime for Symfony`: https://github.com/dunglas/symfony-docker
|
|
.. _`"as" attribute`: https://w3c.github.io/preload/#as-attribute
|
|
.. _`the Priority Hints specification`: https://wicg.github.io/priority-hints/
|
|
.. _`the Preload specification`: https://www.w3.org/TR/preload/#server-push-http-2
|
|
.. _`Cloudflare`: https://blog.cloudflare.com/announcing-support-for-http-2-server-push-2/
|
|
.. _`Fastly`: https://docs.fastly.com/en/guides/http2-server-push
|
|
.. _`Akamai`: https://http2.akamai.com/
|
|
.. _`link defined in the HTML specification`: https://html.spec.whatwg.org/dev/links.html#linkTypes
|
|
.. _`PSR-13`: https://www.php-fig.org/psr/psr-13/
|
|
.. _`Speculation Rules API`: https://developer.mozilla.org/docs/Web/API/Speculation_Rules_API
|