Skip to content

Commit 0bfcf48

Browse files
jamesdanielsleoortizzmfellner
authored
Fix for Next.js POST/PATCH/etc and Server Actions (#122)
As identified in #116 the root cause of the problems with non-get requests in NextJS on Firebase Hosting is that the ExpressJS' readable has been spent on the body-parser middleware. Solution is to create a new Readable from rawBody and pass that to the NextJS request handler. * Proxy express requests through a new IncomingMessage * Drop the LRU cache of next apps, no longer needed --------- Co-authored-by: Leonardo Ortiz <[email protected]> Co-authored-by: Maximilian Fellner <[email protected]>
1 parent 68c8e99 commit 0bfcf48

File tree

2 files changed

+38
-30
lines changed

2 files changed

+38
-30
lines changed
Lines changed: 12 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,23 @@
11
import { parse } from "url";
22
import createNextServer from "next";
3-
import LRU from "lru-cache";
43

54
import type { Request } from "firebase-functions/v2/https";
65
import type { Response } from "express";
76
import type { NextServer } from "next/dist/server/next.js";
7+
import { incomingMessageFromExpress } from "../utils.js";
88

9-
const nextAppsLRU = new LRU<string, NextServer>({
10-
// TODO tune this
11-
max: 3,
12-
allowStale: true,
13-
updateAgeOnGet: true,
14-
dispose: (server) => {
15-
server.close();
16-
},
9+
// @ts-expect-error - Next.js doesn't export the custom server function with proper types
10+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
11+
const nextApp: NextServer = createNextServer({
12+
dev: false,
13+
dir: process.cwd(),
14+
hostname: "0.0.0.0",
15+
port: 443,
1716
});
1817

1918
export const handle = async (req: Request, res: Response) => {
20-
const { hostname, protocol, url } = req;
21-
const port = protocol === "https" ? 443 : 80;
22-
const key = [hostname, port].join(":");
23-
// I wish there was a better way to do this, but it seems like this is the
24-
// way to go. Should investigate more if we can get hostname/port to be
25-
// dynamic for middleware.
26-
let nextApp = nextAppsLRU.get(key);
27-
if (!nextApp) {
28-
// @ts-expect-error - Next.js doesn't export the custom server function with proper types
29-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
30-
nextApp = createNextServer({
31-
dev: false,
32-
dir: process.cwd(),
33-
hostname: "0.0.0.0",
34-
port,
35-
});
36-
nextAppsLRU.set(key, nextApp!);
37-
}
38-
await nextApp!.prepare();
39-
const parsedUrl = parse(url, true);
40-
nextApp!.getRequestHandler()(req, res, parsedUrl);
19+
await nextApp.prepare();
20+
const parsedUrl = parse(req.url, true);
21+
const incomingMessage = incomingMessageFromExpress(req);
22+
await nextApp.getRequestHandler()(incomingMessage, res, parsedUrl);
4123
};

packages/firebase-frameworks/src/utils.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import { Request } from "firebase-functions/v2/https";
2+
import { IncomingMessage } from "node:http";
3+
import { Socket } from "node:net";
4+
15
export async function isUsingFirebaseJsSdk() {
26
if (!process.env.__FIREBASE_DEFAULTS__) return false;
37
try {
@@ -7,3 +11,25 @@ export async function isUsingFirebaseJsSdk() {
711
return false;
812
}
913
}
14+
15+
export function incomingMessageFromExpress(req: Request): IncomingMessage {
16+
const socket = new Socket();
17+
const incomingMessage = new IncomingMessage(socket);
18+
19+
incomingMessage.push(req.rawBody);
20+
incomingMessage.push(null);
21+
22+
incomingMessage.headers = req.headers;
23+
incomingMessage.headersDistinct = req.headersDistinct;
24+
incomingMessage.httpVersion = req.httpVersion;
25+
incomingMessage.httpVersionMajor = req.httpVersionMajor;
26+
incomingMessage.httpVersionMinor = req.httpVersionMinor;
27+
incomingMessage.method = req.method;
28+
incomingMessage.rawHeaders = req.rawHeaders;
29+
incomingMessage.rawTrailers = req.rawTrailers;
30+
incomingMessage.trailers = req.trailers;
31+
incomingMessage.trailersDistinct = req.trailersDistinct;
32+
incomingMessage.url = req.url;
33+
34+
return incomingMessage;
35+
}

0 commit comments

Comments
 (0)