Skip to content

[FEATURE] Support CF Websocket upgrade on NextJS API #784

@reinismu

Description

@reinismu

Is your feature request related to a problem?

Example how to reproduce. In the end durable worker returns the same Response.

import { getCloudflareContext } from "@opennextjs/cloudflare";
import type { NextRequest } from "next/server";

export default async function handler(request: NextRequest) {
  console.log("WebSocket upgrade handler invoked");
    const webSocketPair = new WebSocketPair();
    const [client, server] = Object.values(webSocketPair);

    return new Response(null, { status: 101, webSocket: client });
}

export { handler as GET};

I assume somewhere in NextJS pipeline the webSocket object is lost and we get [ERROR] Uncaught RangeError: Responses may only be constructed with status codes in the range 200 to 599, inclusive.

Describe the solution you'd like

Ability to make nextjs API return our webSocket object

Describe alternatives you've considered

  1. Use seperate worker for websocket API

  2. Rewrite workers.js with hook for handling websockets

//@ts-expect-error: Will be resolved by wrangler build
import { fetchImage } from "./.open-next/cloudflare/images.js";
//@ts-expect-error: Will be resolved by wrangler build
import { runWithCloudflareRequestContext } from "./.open-next/cloudflare/init.js";
//@ts-expect-error: Will be resolved by wrangler build
import { maybeGetSkewProtectionResponse } from "./.open-next/cloudflare/skew-protection.js";
// @ts-expect-error: Will be resolved by wrangler build
import { handler as middlewareHandler } from "./.open-next/middleware/handler.mjs";
import { wsHandler } from "./src/workers/handler.js";
//@ts-expect-error: Will be resolved by wrangler build
export { DOQueueHandler } from "./.open-next/.build/durable-objects/queue.js";
//@ts-expect-error: Will be resolved by wrangler build
export { DOShardedTagCache } from "./.open-next/.build/durable-objects/sharded-tag-cache.js";
//@ts-expect-error: Will be resolved by wrangler build
export { BucketCachePurge } from "./.open-next/.build/durable-objects/bucket-cache-purge.js";

export default {
    async fetch(request: Request, env: any, ctx: any) {
        return runWithCloudflareRequestContext(request, env, ctx, async () => {
            const response = maybeGetSkewProtectionResponse(request);
            if (response) {
                return response;
            }
            const url = new URL(request.url);
            // handle it here
            if (url.pathname.startsWith("/api/ws")) {
              return wsHandler(request);
            }

            // Serve images in development.
            // Note: "/cdn-cgi/image/..." requests do not reach production workers.
            if (url.pathname.startsWith("/cdn-cgi/image/")) {
                const m = url.pathname.match(/\/cdn-cgi\/image\/.+?\/(?<url>.+)$/);
                if (m === null) {
                    return new Response("Not Found!", { status: 404 });
                }
                const imageUrl = m.groups?.url;
                return imageUrl?.match(/^https?:\/\//)
                    ? fetch(imageUrl, { cf: { cacheEverything: true } })
                    : env.ASSETS?.fetch(new URL(`/${imageUrl}`, url));
            }
            // Fallback for the Next default image loader.
            if (url.pathname === `${(globalThis as any).__NEXT_BASE_PATH__}/_next/image`) {
                const imageUrl = url.searchParams.get("url") ?? "";
                return await fetchImage(env.ASSETS, imageUrl, ctx);
            }
            // - `Request`s are handled by the Next server
            const reqOrResp = await middlewareHandler(request, env, ctx);
            if (reqOrResp instanceof Response) {
                return reqOrResp;
            }
            // @ts-expect-error: resolved by wrangler build
            const { handler } = await import("./.open-next/server-functions/default/handler.mjs");
            return handler(reqOrResp, env, ctx);
        });
    },
};

export { ClientHibernationServer } from "./src/workers/clientDO";

@opennextjs/cloudflare version

1.5.1

Additional context

cloudflare/workerd#3047

https://github.com/cloudflare/workerd/blob/7267bc05908fb14677b6c2c5f98a03dba1f68a66/src/workerd/api/http.c%2B%2B#L916C7-L916C16

Before submitting

  • I have checked that there isn't already a similar feature request
  • This is a single feature (not multiple features in one request)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions