diff options
author | 2025-06-05 14:25:23 +0000 | |
---|---|---|
committer | 2025-06-05 14:25:23 +0000 | |
commit | e586d7d704d475afe3373a1de6ae20d504f79d6d (patch) | |
tree | 7e3fa24807cebd48a86bd40f866d792181191ee9 /packages/integrations/web-vitals/src/middleware.ts | |
download | astro-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.ts | 60 |
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('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"'); +} |