symfony/web-link
Symfony WebLink component helps manage link relationships between resources. Create and serialize HTTP Link headers for preload, prefetch, and resource hints (HTML5/Web standards), enabling better performance via HTTP/2 push and client hints.
Installation:
composer require symfony/web-link
No Laravel-specific dependencies; works with PHP 8.0+.
Basic Usage:
Inject a Link header for preloading a critical CSS file in a Laravel controller or middleware:
use Symfony\Component\WebLink\Link;
use Symfony\Component\WebLink\HttpHeaderSerializer;
// In a controller or middleware
$linkProvider = (new \Symfony\Component\WebLink\GenericLinkProvider())
->withLink(new Link('preload', '/css/main.css', ['as' => 'style']));
return response()->header(
'Link',
(new HttpHeaderSerializer())->serialize($linkProvider->getLinks())
);
First Use Case: Preload critical assets for a homepage to improve Largest Contentful Paint (LCP):
// app/Http/Middleware/PreloadCriticalAssets.php
public function handle(Request $request, Closure $next)
{
$response = $next($request);
$linkProvider = (new GenericLinkProvider())
->withLink(new Link('preload', asset('css/main.css'), ['as' => 'style']))
->withLink(new Link('preload', asset('fonts/roboto.woff2'), ['as' => 'font', 'crossorigin' => '']));
return $response->header(
'Link',
(new HttpHeaderSerializer())->serialize($linkProvider->getLinks())
);
}
Register the middleware in app/Http/Kernel.php:
protected $middleware = [
\App\Http\Middleware\PreloadCriticalAssets::class,
];
Where to Look First:
Link class constants (e.g., Link::AS_STYLE, Link::AS_SCRIPT) for standardized attributes.HttpHeaderSerializer for header formatting.Dynamically add preload headers based on the current route:
// app/Providers/RouteServiceProvider.php
public function boot()
{
$router->preloadAssets(function (Request $request) {
$links = [];
if ($request->is('home')) {
$links[] = new Link('preload', asset('css/home.css'), ['as' => 'style']);
}
if ($request->is('dashboard')) {
$links[] = new Link('preload', asset('js/dashboard.js'), ['as' => 'script']);
}
return $links;
});
}
Use middleware to inject these links:
// app/Http/Middleware/InjectRouteLinks.php
public function handle(Request $request, Closure $next)
{
$links = app()->make('router')->preloadAssets($request);
$linkProvider = (new GenericLinkProvider())->withLinks(...$links);
return $next($request)->header(
'Link',
(new HttpHeaderSerializer())->serialize($linkProvider->getLinks())
);
}
Integrate with Laravel Mix/Vite to auto-generate preload links for compiled assets:
// app/Providers/AppServiceProvider.php
public function boot()
{
if ($this->app->environment('production')) {
$mixManifest = json_decode(file_get_contents(public_path('mix-manifest.json')), true);
$links = collect($mixManifest)
->filter(fn ($path) => str_contains($path, '.css') || str_contains($path, '.js'))
->map(fn ($path) => new Link(
'preload',
asset($path),
['as' => str_contains($path, '.css') ? 'style' : 'script']
))
->toArray();
$this->app->singleton('preload.link-provider', fn() => (new GenericLinkProvider())->withLinks(...$links));
}
}
Inject in middleware:
return $response->header(
'Link',
(new HttpHeaderSerializer())->serialize(app('preload.link-provider')->getLinks())
);
Add navigation links to API responses:
// app/Http/Controllers/API/PostController.php
public function show(Post $post)
{
$linkProvider = (new GenericLinkProvider())
->withLink(new Link('related', route('posts.index'), ['title' => 'All Posts']))
->withLink(new Link('next', route('posts.index', ['page' => 2]), ['title' => 'Next Page']));
return response()->json($post)->header(
'Link',
(new HttpHeaderSerializer())->serialize($linkProvider->getLinks())
);
}
Inject PWA-related links for offline support:
$linkProvider = (new GenericLinkProvider())
->withLink(new Link('manifest', asset('manifest.json')))
->withLink(new Link('apple-touch-icon', asset('icons/apple-touch-icon.png')))
->withLink(new Link('icon', asset('favicon.ico')));
Service Container Binding:
Bind the GenericLinkProvider to the container for reuse:
$this->app->singleton(GenericLinkProvider::class, fn() => new GenericLinkProvider());
Then inject it into controllers/middleware:
public function __construct(private GenericLinkProvider $linkProvider) {}
Response Macros:
Extend Laravel’s Response class to simplify header injection:
// app/Extensions/Response.php
use Symfony\Component\WebLink\HttpHeaderSerializer;
Response::macro('withLinks', function (array $links) {
$linkProvider = (new GenericLinkProvider())->withLinks(...$links);
return $this->header('Link', (new HttpHeaderSerializer())->serialize($linkProvider->getLinks()));
});
Usage:
return response()->json($data)->withLinks([
new Link('preload', asset('css/app.css'), ['as' => 'style']),
]);
Caching Links: Cache precomputed links for static routes (e.g., homepage):
$links = Cache::remember('preload.homepage.links', now()->addHours(1), function () {
return [
new Link('preload', asset('css/home.css'), ['as' => 'style']),
new Link('preload', asset('fonts/roboto.woff2'), ['as' => 'font']),
];
});
Blade Directives:
Create a Blade directive to output <link> tags for client-side preloading:
// app/Providers/BladeServiceProvider.php
Blade::directive('preload', function ($asset) {
return "<?php echo '<link rel=\"preload\" href=\"'.$asset.'\" as=\"'.pathinfo($asset, PATHINFO_EXTENSION).'\" crossorigin=\"\">'; ?>";
});
Usage in Blade:
@preload(mix('css/app.css'))
For HTTP/2 server push, configure your web server (e.g., Nginx, Apache) to respect Link headers:
# Nginx example
location / {
add_header Link "</css/main.css>; rel=preload; as=style" always;
# Or dynamically via Laravel:
# proxy_set_header Link $http_link;
}
Note: Server push requires HTTPS and explicit configuration.
Header Injection Timing:
Link headers after the response is sent (e.g., in a finished middleware) will fail.// Wrong: Runs after response is sent
$response->onFinish(function () use ($response) {
$response->header('Link', '...'); // Too late!
});
// Correct: Runs during response generation
return $response->header('Link', '...');
Duplicate Links:
Link headers, causing malformed headers.GenericLinkProvider or merge links before serialization:
$linkProvider = app(GenericLinkProvider::class);
$linkProvider->withLink($newLink);
$serializer = new HttpHeaderSerializer();
$serializer->setLinks($linkProvider->getLinks()); // Overwrite duplicates
Cross-Origin Restrictions:
How can I help you explore Laravel packages today?