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.

Symfony は、Link HTTP ヘッダーを管理するための (WebLink コンポーネントを介した) ネイティブ サポートを提供します。これは、HTTP/2 を使用し、最新の Web ブラウザーのプリロード機能を使用する場合に、アプリケーションのパフォーマンスを向上させるための鍵となります。

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:

リンク ヘッダーは、HTTP/2 サーバー プッシュと W3C のリソース ヒントで使用され、リソース (CSS や JavaScript ファイルなど) をクライアントが必要とする前にクライアントにプッシュします。 WebLink は、HTTP 1.x で動作する他の最適化も有効にします。
  • Asking the browser to fetch or to render another web page in the background;
    バックグラウンドで別の Web ページをフェッチまたはレンダリングするようブラウザに要求する。
  • Making early DNS lookups, TCP handshakes or TLS negotiations.
    初期の DNS ルックアップ、TCP ハンドシェイク、または TLS ネゴシエーションを行います。

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.

考慮すべき重要な点は、ローカル マシンで作業している場合でも、これらすべての HTTP/2 機能には安全な HTTPS 接続が必要であるということです。主な Web サーバー (Apache、nginx、Caddy など) はこれをサポートしていますが、Symfony コミュニティの Kévin Dunglas によって作成された Symfony の Docker インストーラーとランタイムを使用することもできます。

Preloading Assets

Imagine that your application includes a web page like this:

アプリケーションに次のような Web ページが含まれているとします。
1
2
3
4
5
6
7
8
9
10
11
12
13
<!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>

Following the traditional HTTP workflow, when this page is served browsers will make one request for the HTML page and another request for the linked CSS file. However, thanks to HTTP/2 your application can start sending the CSS file contents even before browsers request them.

従来の HTTP ワークフローに従って、このページが提供されると、ブラウザーは HTML ページに対して 1 つの要求を行い、リンクされた CSS ファイルに対して別の要求を行います。ただし、HTTP/2 のおかげで、ブラウザーが要求する前であっても、アプリケーションは CSS ファイルのコンテンツの送信を開始できます。

To do that, first install the WebLink component:

そのためには、まず WebLink コンポーネントをインストールします。
1
$ composer require symfony/web-link

Now, update the template to use the preload() Twig function provided by WebLink. The "as" attribute is mandatory because browsers need it to apply correct prioritization and the content security policy:

ここで、WebLink が提供する preload() Twig 関数を使用するようにテンプレートを更新します。 「as」属性は必須です。これは、ブラウザーが正しい優先順位付けとコンテンツ セキュリティ ポリシーを適用するために必要だからです。
1
2
3
4
<head>
    <!-- ... -->
    <link rel="preload" href="{{ preload('/app.css', { as: 'style' }) }}">
</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.

ページをリロードすると、ブラウザが HTML ページのみを要求したときにサーバーが HTML ページと CSS ファイルの両方で応答するため、知覚されるパフォーマンスが向上します。

Note

ノート

You can preload an asset by wrapping it with the preload() function:

preload() 関数でラップすることにより、アセットをプリロードできます。
1
2
3
4
<head>
    <!-- ... -->
    <link rel="preload" href="{{ preload(asset('build/app.css')) }}">
</head>

Additionally, according to the Priority Hints specification, you can signal the priority of the resource to download using the importance attribute:

さらに、Priority Hints 仕様に従って、重要度属性を使用してダウンロードするリソースの優先度を通知できます。
1
2
3
4
<head>
    <!-- ... -->
    <link rel="preload" href="{{ preload('/app.css', { as: 'style', importance: 'low' }) }}">
</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.

WebLink コンポーネントは、応答に追加された Link HTTP ヘッダーを管理します。前の例で preload() 関数を使用すると、次のヘッダーが応答に追加されました。 rel="プリロード"; as="style"Preload 仕様によると、元の (HTTP 1.x) 応答にこの HTTP ヘッダーが含まれていることを HTTP/2 サーバーが検出すると、同じ HTTP/2 接続で関連ファイルのプッシュが自動的にトリガーされます。

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.

Cloudflare、Fastly、Akamai などの一般的なプロキシ サービスと CDN もこの機能を利用しています。これは、リソースをクライアントにプッシュして、本番環境でアプリケーションのパフォーマンスを今すぐ改善できることを意味します。

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:

プッシュを防止したいが、早期に別の HTTP リクエストを発行してブラウザにリソースをプリロードさせたい場合は、nopush オプションを使用します。
1
2
3
4
<head>
    <!-- ... -->
    <link rel="preload" href="{{ preload('/app.css', { as: 'style', nopush: true }) }}">
</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:

WebLink コンポーネントは、これらのヒントを送信するために次の Twig 関数を提供します。
  • 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".
    dns_prefetch(): 「必要なリソースを取得するために使用されるオリジン (例: https://foo.cloudfront.net) を示し、ユーザー エージェントはできるだけ早く解決する必要があります」.
  • 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".
    preconnect(): "必要なリソースを取得するために使用されるオリジン (例: https://www.google-analytics.com) を示します。DNS ルックアップ、TCP ハンドシェイク、およびオプションの TLS ネゴシエーションを含む早期接続を開始します。ユーザー エージェントは、接続を確立するための高いレイテンシ コストを隠すことができます。」
  • 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".
    prefetch(): 「次のナビゲーションで必要となる可能性のあるリソースを識別し、ユーザー エージェントがフェッチする必要があることを示します。これにより、将来リソースが要求されたときにユーザー エージェントがより迅速に応答できるようになります」.
  • prerender(): "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".
    prerender(): 「次のナビゲーションで必要となる可能性のあるリソースを識別し、後でリソースが要求されたときにユーザー エージェントがより迅速な応答を提供できるように、ユーザー エージェントが取得して実行する必要があります」.

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:

このコンポーネントは、パフォーマンスに関係のない HTTP リンクと、PSR-13 標準を実装する任意のリンクの送信もサポートしています。たとえば、HTML 仕様で定義されている anylink は次のようになります。
1
2
3
4
5
<head>
    <!-- ... -->
    <link rel="alternate" href="{{ link('/index.jsonld', 'alternate') }}">
    <link rel="preload" href="{{ preload('/app.css', { as: 'style', nopush: true }) }}">
</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

前のスニペットでは、この HTTP ヘッダーが client:Link: に送信されます。 rel="代替",; rel="プリロード"; nopush

You can also add links to the HTTP response directly from controllers and services:

コントローラーとサービスから直接 HTTP 応答へのリンクを追加することもできます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\WebLink\GenericLinkProvider;
use Symfony\Component\WebLink\Link;

class BlogController extends AbstractController
{
    public function index(Request $request)
    {
        // using the addLink() shortcut provided by AbstractController
        $this->addLink($request, new Link('preload', '/app.css'));

        // 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('...');
    }
}