alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
86 lines (74 loc) • 2.45 kB
text/typescript
import { $hook, $inject, Alepha } from "alepha";
import { SSRManifestProvider } from "./SSRManifestProvider.ts";
/**
* Adds HTTP Link headers for preloading entry assets.
*
* Benefits:
* - Early Hints (103): Servers can send preload hints before the full response
* - CDN optimization: Many CDNs use Link headers to optimize asset delivery
* - Browser prefetching: Browsers can start fetching resources earlier
*
* The Link header is computed once at first request and cached for reuse.
*/
export class ReactPreloadProvider {
protected readonly alepha = $inject(Alepha);
protected readonly ssrManifest = $inject(SSRManifestProvider);
/**
* Cached Link header value - computed once, reused for all requests.
*/
protected cachedLinkHeader: string | null | undefined;
/**
* Build the Link header string from entry assets.
*
* Format: <url>; rel=preload; as=type, <url>; rel=modulepreload
*
* @returns Link header string or null if no assets
*/
protected buildLinkHeader(): string | null {
const assets = this.ssrManifest.getEntryAssets();
if (!assets) return null;
const links: string[] = [];
// CSS - preload as style
for (const css of assets.css) {
links.push(`<${css}>; rel=preload; as=style`);
}
// JS - modulepreload for ES modules
if (assets.js) {
links.push(`<${assets.js}>; rel=modulepreload`);
}
return links.length > 0 ? links.join(", ") : null;
}
/**
* Get the cached Link header, computing it on first access.
*/
protected getLinkHeader(): string | null {
if (this.cachedLinkHeader === undefined) {
this.cachedLinkHeader = this.buildLinkHeader();
}
return this.cachedLinkHeader;
}
/**
* Add Link header to HTML responses for asset preloading.
*/
protected readonly onResponse = $hook({
on: "server:onResponse",
priority: "first",
handler: ({ response }) => {
// Only add to HTML responses (SSR pages)
const contentType = response.headers["content-type"];
if (!contentType?.includes("text/html")) {
return;
}
const linkHeader = this.getLinkHeader();
if (!linkHeader) {
return;
}
// Append to existing Link header if present
if (response.headers.link) {
response.headers.link = `${response.headers.link}, ${linkHeader}`;
} else {
response.headers.link = linkHeader;
}
},
});
}