diff --git a/.changeset/olive-foxes-own.md b/.changeset/olive-foxes-own.md new file mode 100644 index 00000000000..5d411d8e05a --- /dev/null +++ b/.changeset/olive-foxes-own.md @@ -0,0 +1,5 @@ +--- +"@clerk/astro": patch +--- + +Improve stream processing performance diff --git a/packages/astro/src/server/clerk-middleware.ts b/packages/astro/src/server/clerk-middleware.ts index 753df636cd8..be08f6642af 100644 --- a/packages/astro/src/server/clerk-middleware.ts +++ b/packages/astro/src/server/clerk-middleware.ts @@ -228,6 +228,16 @@ function decorateAstroLocal(req: Request, context: APIContext, requestState: Req context.locals.currentUser = createCurrentUser(req, context); } +/** + * Find the index of the closing head tag in the chunk. + * + * Note: This implementation uses a simple approach that works for most of our + * current use cases. + */ +function findClosingHeadTagIndex(chunk: Uint8Array, endHeadTag: Uint8Array) { + return chunk.findIndex((_, i) => endHeadTag.every((value, j) => value === chunk[i + j])); +} + async function decorateRequest( locals: APIContext['locals'], res: Response, @@ -245,48 +255,42 @@ async function decorateRequest( * without sucrificing DX and having developers wrap each page with a Layout that would handle this. */ if (res.headers.get('content-type') === 'text/html') { - const reader = res.body?.getReader(); - const stream = new ReadableStream({ - async start(controller) { - let { value, done } = await reader!.read(); - const encoder = new TextEncoder(); - const decoder = new TextDecoder(); - while (!done) { - const decodedValue = decoder.decode(value); + const encoder = new TextEncoder(); + const closingHeadTag = encoder.encode(''); + const clerkAstroData = encoder.encode( + `\n`, + ); + const clerkSafeEnvVariables = encoder.encode( + `\n`, + ); + const hotloadScript = encoder.encode(buildClerkHotloadScript(locals)); + + const stream = res.body!.pipeThrough( + new TransformStream({ + transform(chunk, controller) { + const index = findClosingHeadTagIndex(chunk, closingHeadTag); + const isClosingHeadTagFound = index !== -1; /** * Hijack html response to position `__CLERK_ASTRO_DATA__` before the closing `head` html tag */ - if (decodedValue.includes('')) { - const [p1, p2] = decodedValue.split(''); - controller.enqueue(encoder.encode(p1)); - controller.enqueue( - encoder.encode( - `\n`, - ), - ); - - controller.enqueue( - encoder.encode( - `\n`, - ), - ); + if (isClosingHeadTagFound) { + controller.enqueue(chunk.slice(0, index)); + controller.enqueue(clerkAstroData); + controller.enqueue(clerkSafeEnvVariables); if (__HOTLOAD__) { - controller.enqueue(encoder.encode(buildClerkHotloadScript(locals))); + controller.enqueue(hotloadScript); } - controller.enqueue(encoder.encode('')); - controller.enqueue(encoder.encode(p2)); + controller.enqueue(closingHeadTag); + controller.enqueue(chunk.slice(index + closingHeadTag.length)); } else { - controller.enqueue(value); + controller.enqueue(chunk); } - - ({ value, done } = await reader!.read()); - } - controller.close(); - }, - }); + }, + }), + ); const modifiedResponse = new Response(stream, { status: res.status,