diff options
author | 2024-05-03 17:40:53 +0200 | |
---|---|---|
committer | 2024-05-03 17:40:53 +0200 | |
commit | a37d76a42ac00697be3acd575f3f7163129ea75c (patch) | |
tree | 99c2c542e07eb49643cb682cecc31250dfd8bd9e /packages/integrations/web-vitals/src/middleware.ts | |
parent | befbda7fa3d712388789a5a9be1e0597834f86db (diff) | |
download | astro-a37d76a42ac00697be3acd575f3f7163129ea75c.tar.gz astro-a37d76a42ac00697be3acd575f3f7163129ea75c.tar.zst astro-a37d76a42ac00697be3acd575f3f7163129ea75c.zip |
Add web-vitals integration (#10883)
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..b4994c902 --- /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('"', '"'); +} |