Response.text() Returns Garbled Text

Hi. In an Edge function, when I run:

const response = await context.next()
const text = response.text()
console.log(text)

The result is garbled text.

Through eliminations, I believe this result was due to having <meta> tags.

 <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

For whatever reason, the response received wasn’t properly encoded.

The page in problem

Here is the file that generates HTML for that page above.

Adding a trailing slash: debug-unreadable-body–static-to-dynamic.netlify.app/cache-keys/ seems to solve the problem. So it doesn’t appear to be related to the meta tags, but could be something going wrong internally. I’ll ask the devs to check.

1 Like

Hey there! Simon from the Edge Functions team here. Welcome back to the forums :tada:

I checked your code, and have a hypothesis of what’s happening:

By default, the Netlify CDN redirects requests from /foo to /foo/ (see docs). That means that for requests to /cache-keys, context.next() will return a Response with an empty body and a 301 status. Your code doesn’t take that into account. It takes that empty body and uses it in the 200 response. I’d expect that to result in an empty body, but apparently it results in garbled text, for whatever reason. I’ll make a note internally to investigate that, but I think this unrelated to your issue.

To check this hypothesis, could you try changing your edge function so that it only modifies 200 requests? A little like this:

import type { Config, Context } from "@netlify/edge-functions"

export default async (request: Request, context: Context) => {
  const response = await context.next()
+ if (response.status !== 200) return response
  const text = await response.text()
  console.log(text)
  return new Response(text, {
    headers: {
      "content-type": "text/html;charset=utf-8",
      "Cache-Control": "public, max-age=0, must-revalidate", // Tell browsers to always revalidate
      "Netlify-CDN-Cache-Control": "public, max-age=31536000, must-revalidate",
    }
  })
}

export const config: Config = {
  path: "/cache-keys"
}

Also, bonus tip - always try to prevent calling response.text if you can. That will buffer the response in memory and decrease your time to first byte significantly. You’re probably already aware of this and just used it for debugging purposes - still wanted to point it out :smiley:

1 Like

Thanks for getting back. Your suggestion works.
Did you mean “prevent logging response.text”? Because I intend to modify the response, so there is no way getting around calling await response.text()?

If you only need to attach caching headers, then there’s no need to call response.text. Here’s an example:

export default async (request, context) => {
   const response = await context.next()
   return new Response(response.body, {
      headers: {
           "foo": "bar",
           ...response.headers // this is important, so you don't swallow headers!
      }
   })
}

Or, even simpler:

export default async (request, context) => {
   const response = await context.next()
   response.headers.set("foo", "bar")
   return response
}

If your usecase requires modifying the response body, then you can do that using a Streaming Transform:

export default async (request, context) => {
   const response = await context.next()
   const transformer = new TransformStream({
      transform(chunk, controller) {
         controller.enqueue(doTransform(chunk))
      }
   })
   return new Response(response.body.pipeThrough(transformer), response)
}

Compared to response.text(), which buffers the full response body before you can process it, this TransformStream approach allows you to transform the body as it arrives.

This is not an optimisation you absolutely need, but if your site has performance constraints (think marketing pages or e-commerce) then you should always try to prevent buffering.

1 Like

Hi, just an update: Does the streaming transform work for you? I got an error “Readable stream is unlocked”. May be I don’t need to read stream, but I’d like to explore if it is possible.

That sounds like you’re trying to read the stream and then transform it. That won’t work, you can only have a single reader - take a look at ReadableStream: tee() method - Web APIs | MDN if you absolutely need to read it twice.