Skip to content

Instantly share code, notes, and snippets.

@cramforce
Last active November 4, 2022 22:37
Show Gist options
  • Save cramforce/12e58aea2920ae80159f42be451e8027 to your computer and use it in GitHub Desktop.
Save cramforce/12e58aea2920ae80159f42be451e8027 to your computer and use it in GitHub Desktop.
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
const SCRIPT_TAG = '<script async src="/feedback-bootstrap.js"></script>';
export async function middleware(request: NextRequest) {
// Skip requests not looking for html.
const accept = request.headers.get("accept") || "";
if (!accept) {
console.log("Request headers", request.headers);
}
if (!accept || !accept.startsWith("text/html")) {
return NextResponse.next();
}
const middlewareResponse = NextResponse.next();
// Skip redirects, etc.
if (middlewareResponse.status > 200) {
return middlewareResponse;
}
// We want to change the response, and that means we have to fetch it ourselves.
const originResponse = await fetch(request);
// Response doesn't look like HTML, skip!
const contentType = originResponse.headers.get("content-type");
if (!contentType) {
console.log("Origin response headers", originResponse.headers);
}
if (!contentType || !contentType.startsWith("text/html")) {
return originResponse;
}
let rewritten;
if (originResponse.body.getReader) {
console.log("Using the streaming API");
const reader = originResponse.body.getReader();
rewritten = new ReadableStream({
start(controller) {
function push() {
return reader.read().then(({ done, value }) => {
if (done) {
// Append the script tag.
controller.enqueue(new TextEncoder().encode(SCRIPT_TAG));
controller.close();
return;
}
controller.enqueue(value);
push();
});
}
push();
},
});
} else {
// It appears that originResponse.body.getReader is null and so we cannot stream the
// addition.
const text = await originResponse.text();
// Dumping it to the end is simple and works.
rewritten = text + SCRIPT_TAG;
}
return new Response(rewritten, {
headers: new Headers(originResponse.headers),
status: originResponse.status,
statusText: originResponse.statusText,
});
}
@karaggeorge
Copy link

The idea is that if other middlewares do a redirect, then this one should do nothing. It would likely also check whether there is a body in case the middleware actually responded.

But NextResponse.next() doesn't actually run anything else. It's just a static object (so that check will always be false):

static next() {
    return new NextResponse(null, {
      headers: {
        'x-middleware-next': '1',
      },
    })
  }

It's used to check the response at the end of the middleware call to know what to do next. I think it's mostly meant to be used if you want to let the request fall through, but attach some extra headers.

As far as I can see the streaming interface on the fetch response is broken.

Yeah, it's a middleware issue, we have a workaround cleanResponseBody method in Live to get the correct ReadableStream (currently it returns a Node.js stream)

--

The rest looks good. Only worry here is if the user has an existing top-level middleware, I don't know how this will play out (how do we merge them)

This is pretty much exactly the way I implemented it locally as well, only difference was the streaming

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment