How To - NextJS + Websockets + http-only cookies

This post should be quick - I just need to document the pain. I hope no one else goes through it. I very nearly just rolled my own bundler/server (again) just to get this all to work.

Problem 1: first and foremost, the nextjs server just doesn't really support websockets, nor does it support proxying websockets

This in of itself isn't too crazy you might say - just spin up another server sitting next to it and you'll be fine right?

Problem 2: Well what if you want to use httpOnly cookies? You'll need some sort of proxy in front of both of your servers

I tried http-proxy, I tried next-http-proxy-middleware, and they all had problems, and I really couldn't configure them to work. I was ready to roll my own, then I decided to do something way less stupid and just nginx. That was the right call, and its working pretty well with this config:

Next you gotta do something to create a session token on page load right? Let's try using nextjs' middleware.

It turns out you can't do a whole lot of stuff in nextjs middleware. For me, I've been using this beautiful postgres library, and for some reason i was getting a strange error when I tried to use it from my middleware:


Module build failed: UnhandledSchemeError: Reading from "cloudflare:sockets" is not handled by plugins (Unhandled scheme).
Webpack supports "data:" and "file:" URIs by default.
You may need an additional plugin to handle "cloudflare:" URIs.
Import trace for requested module:
cloudflare:sockets
./node_modules/.pnpm/postgres@3.4.3/node_modules/postgres/cf/polyfills.js
./node_modules/.pnpm/postgres@3.4.3/node_modules/postgres/cf/src/index.js
./src/utils/sql.ts

This is weird - I'm not using cloudflare/wrangler at all. Your error may be different... but all roads lead to the following problem:

Problem 3: NextJS middleware can't use node libraries and always acts like its running in on an edge node of CDN

I don't perfectly know how all this works, but I know that the postgres library defines exports in its package.json file, and one of those exports point to the edge cdn version of the library, somehow the nextjs bundler indicates that it wants edge versions of npm libraries, I think webpack is involved some how here too. This eventually lead to my very opaque, confusing error.

After a few dozen google searches, I eventually learned these caveats about the nextjs middleware, and learned there's absolutely no way to make it work like its just running on plain ol node.js, even if you were running your own servers.

The only solution I could think of at this point was creating another API that would create/validate tokens and the middleware would just fetch and set the cookie like so:


import { serialize } from 'cookie';
import { DateTime } from 'luxon';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export async function middleware(request: NextRequest) {
  const originalSessionToken = request.cookies.get('sess')?.value ?? '';
  console.log('Original Sesh Token', originalSessionToken);
  const response = await fetch(`${process.env.NEXT_PUBLIC_HOST}/api/your-api-route` ?? '', {
    method: 'POST',
    body: JSON.stringify({
      sessionToken: originalSessionToken,
    }),
  });
  const responseBody = await response.json();
  const newSessionToken = JSON.parse(responseBody.message)?.sessionToken ?? 'FAIL';

  if (originalSessionToken !== newSessionToken) {
    const domain = process.env.NEXT_PUBLIC_HOST;
    const response = NextResponse.next();
    response.headers.set(
      'set-cookie',
      serialize('sess', newSessionToken, {
        path: '/',
        // domain,
        httpOnly: true,
        sameSite: domain?.includes('localhost') ? 'lax' : 'strict',
        expires: DateTime.now().plus({ years: 100 }).toJSDate(),
      }),
    );
    if (originalSessionToken.length !== 0) {
      response.headers.set(
        'set-cookie',
        serialize('logged-out', 'true', {
          path: '/',
          // domain,
          httpOnly: false,
          expires: DateTime.now().plus({ seconds: 10 }).toJSDate(),
          sameSite: domain?.includes('localhost') ? 'lax' : 'strict',
        }),
      );
    }
    return response;
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};

Mini-problem 4: nextjs' cookie library doesn't let you configure your cookies, you just set them.... okkkkk whatever I'll just do that myself too.

Alright, that's about it - everything seems to be working now and we got websockets, http only cookies and nextjs all under 1 roof.

Got questions - drop me a line over at X/Twitter.