aboutsummaryrefslogtreecommitdiff
path: root/packages/integrations/web-vitals/src/middleware.ts
diff options
context:
space:
mode:
authorGravatar github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 2025-06-05 14:25:23 +0000
committerGravatar github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 2025-06-05 14:25:23 +0000
commite586d7d704d475afe3373a1de6ae20d504f79d6d (patch)
tree7e3fa24807cebd48a86bd40f866d792181191ee9 /packages/integrations/web-vitals/src/middleware.ts
downloadastro-latest.tar.gz
astro-latest.tar.zst
astro-latest.zip
Sync from a8e1c0a7402940e0fc5beef669522b315052df1blatest
Diffstat (limited to 'packages/integrations/web-vitals/src/middleware.ts')
-rw-r--r--packages/integrations/web-vitals/src/middleware.ts60
1 files changed, 60 insertions, 0 deletions
diff --git a/packages/integrations/web-vitals/src/middleware.ts b/packages/integrations/web-vitals/src/middleware.ts
new file mode 100644
index 000000000..1935b78d7
--- /dev/null
+++ b/packages/integrations/web-vitals/src/middleware.ts
@@ -0,0 +1,60 @@
+import type { MiddlewareHandler } from 'astro';
+
+/**
+ * Middleware which adds the web vitals `<meta>` tag to each page’s `<head>`.
+ *
+ * @example
+ * <meta name="x-astro-vitals-route" content="/blog/[slug]" />
+ */
+export const onRequest: MiddlewareHandler = async ({ params, url }, next) => {
+ const response = await next();
+ const contentType = response.headers.get('Content-Type');
+ if (contentType !== 'text/html') return response;
+ const webVitalsMetaTag = getMetaTag(url, params);
+ return new Response(
+ response.body
+ ?.pipeThrough(new TextDecoderStream())
+ .pipeThrough(HeadInjectionTransformStream(webVitalsMetaTag))
+ .pipeThrough(new TextEncoderStream()),
+ response,
+ );
+};
+
+/** TransformStream which injects the passed HTML just before the closing </head> tag. */
+function HeadInjectionTransformStream(htmlToInject: string) {
+ let hasInjected = false;
+ return new TransformStream({
+ transform: (chunk, controller) => {
+ if (!hasInjected) {
+ const headCloseIndex = chunk.indexOf('</head>');
+ if (headCloseIndex > -1) {
+ chunk = chunk.slice(0, headCloseIndex) + htmlToInject + chunk.slice(headCloseIndex);
+ hasInjected = true;
+ }
+ }
+ controller.enqueue(chunk);
+ },
+ });
+}
+
+/** Get a `<meta>` tag to identify the current Astro route. */
+function getMetaTag(url: URL, params: Record<string, string | undefined>) {
+ let route = url.pathname;
+ for (const [key, value] of Object.entries(params)) {
+ if (value) route = route.replace(value, `[${key}]`);
+ }
+ route = miniEncodeAttribute(stripTrailingSlash(route));
+ return `<meta name="x-astro-vitals-route" content="${route}" />`;
+}
+
+function stripTrailingSlash(str: string) {
+ return str.length > 1 && str.at(-1) === '/' ? str.slice(0, -1) : str;
+}
+
+function miniEncodeAttribute(str: string) {
+ return str
+ .replaceAll('&', '&amp;')
+ .replaceAll('<', '&lt;')
+ .replaceAll('>', '&gt;')
+ .replaceAll('"', '&quot;');
+}