-
Goals
Basically to allow for checking this last box at the bottom right (thanks for the idea for the diagram @thescientist13)
Non-Goals
Use CasesRedirect logged out usersMiddleware for multiple protected app routes to:
It is point 2 "Query PostgreSQL..." that is currently not possible without overhead. Let's examine the overhead: With
|
.env.* |
middleware.ts |
app/api/users/current/route.ts |
database/users.ts |
---|---|---|---|
API_BASE_URL=http://localhost:3000 |
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export default async function middleware(request: NextRequest) {
const userResponse = await fetch(`${process.env.API_BASE_URL}/users/current`, {
method: 'GET',
headers: {
// Forward cookie to Route Handler
cookie: request.headers.cookie,
},
});
if (!('user' in userResponse)) {
return new NextResponse(null, {
status: 307,
headers: {
'Set-Cookie': 'sessionToken=; Max-Age=-1; Path=/',
location: `/login?returnTo=${request.nextUrl.pathname}`,
},
});
}
return NextResponse.next();
} |
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';
import { getUserBySessionToken } from '../../../database/users';
export async function GET() {
const cookieStore = cookies();
const token = cookieStore.get('sessionToken');
const user = await getUserBySessionToken(token.value);
if (!user) return NextResponse.json({ error: 'user not found' });
return NextResponse.json(user);
} |
export const getUserBySessionToken = cache(async (token: string) => {
const [user] = await sql<
{ id: number; username: string }[]
>`
SELECT
users.id,
users.username
FROM
users
INNER JOIN
sessions ON (
sessions.token = ${token} AND
sessions.user_id = users.id AND
sessions.expiry_timestamp > now()
)
`;
return user;
}); |
With nodejs
runtime
- Usage of Middleware
- Usage of a database query function
middleware.ts |
database/users.ts |
---|---|
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { getUserBySessionToken } from './database/users';
export default async function middleware(request: NextRequest) {
const user = await getUserBySessionToken(request.cookies.token);
if (!user) {
return new NextResponse(null, {
status: 307,
headers: {
'Set-Cookie': 'sessionToken=; Max-Age=-1; Path=/',
location: `/login?returnTo=${request.nextUrl.pathname}`,
},
});
}
return NextResponse.next();
} |
export const getUserBySessionToken = cache(async (token: string) => {
const [user] = await sql<
{ id: number; username: string }[]
>`
SELECT
users.id,
users.username
FROM
users
INNER JOIN
sessions ON (
sessions.token = ${token} AND
sessions.user_id = users.id AND
sessions.expiry_timestamp > now()
)
`;
return user;
}); |
Background
Examples of confusion
- RFC: Switchable Next.js Runtime #34179 (comment)
- RFC: Switchable Next.js Runtime #34179 (comment)
- RFC: Switchable Next.js Runtime #34179 (reply in thread)
Proposal
Middlewares should have a flag experimental.runtime
(similar to the RFC posted by @shuding ), which can also be set to the value 'nodejs'
Beta Was this translation helpful? Give feedback.
Replies: 94 comments 153 replies
-
I've been wanting the same for a while. My reading of the situation is that the problem is one of two worlds:
For those running Next in the first way, which is usually me for most projects at work, it feels pointless to have middleware be restricted to only supporting the edge runtime. Plenty of things which would logically want to be there, like managing rewrites for A/B testing, are handicapped because most 3rd parties are only providing Node SDKs for this kind of thing. For those running Next in the second way, limiting middleware to the edge runtime conceptually makes a lot of sense, because otherwise people are being handed a performance footgun. I'd be happy with edge as the default, and maybe a warning (suppressible) if you switch it to Node. |
Beta Was this translation helpful? Give feedback.
-
I'm trying desperately to do anything at all with RSCs at work, but can do nothing until Middleware can "run" in Node. And I put "run" in scare quotes since this is a self-hosted app, and middleware is already running in Node. |
Beta Was this translation helpful? Give feedback.
-
Workaround (PostgreSQL using Vercel Postgres / Neon Database)For PostgreSQL, it looks like some new packages for the upcoming Vercel Postgres service are enabling database communication in the Vercel Edge Runtime using a WebSocket-to-TCP proxy (Cloudflare blog post):
It does come with some caveats:
|
Beta Was this translation helpful? Give feedback.
-
@leerob Thanks! ✌️ |
Beta Was this translation helpful? Give feedback.
-
Alternative: Improve Node.js API support for more Capable Edge RuntimesSome non-Vercel platforms and edge runtimes mentioned below support more capabilities / APIs from Node.js. This may provide a path forward for the industry - fewer limitations on edge runtimes. More capable edge runtimes may be able to solve most of the issues that people have with edge runtimes. This may offer an alternative for Next.js Middleware - to instead add more Node.js capabilities to edge runtime environments. Deno Deploy supports Node.js builtins at the edgeDeno Deploy supports Node.js builtins as of 26 May 2023:
Source:
Cloudflare Workers TCP
|
Beta Was this translation helpful? Give feedback.
-
I encountered this problem today while attempting to use the MongoDB driver within the middleware. It has taken a while to work out what the actual problem is. I think there might be a workaround within the top-level layout module for my specific use-case but it's an unexpected and disappointing problem to run into while using Next. Being able to switch the runtime used by the middleware would be very much appreciated 👍 |
Beta Was this translation helpful? Give feedback.
-
really need this request! hope soon come |
Beta Was this translation helpful? Give feedback.
-
Ran into the same issue while trying to connect to Redis to implement an authentication flow. Our team uses middleware for a cleaner and logical code flow rather than for performance benefits. Being able to run middleware on the centralized server using the Node runtime would make using middleware much more viable for our use case. Would really appreciate if this feature could be added to the roadmap! |
Beta Was this translation helpful? Give feedback.
-
Thanks for this discussion. I was really confused. Same here. I try to authenticate a request (implementation of the authenticate function of the doc) and can't because the node crypto is unavailable. What is really strange is that it works on route handlers (api route), which I use for OAuth authentication. |
Beta Was this translation helpful? Give feedback.
-
As per this twitter discussion: https://twitter.com/AxelVaindal/status/1676581063638786048 The use case is the following:
Unfortunately, Firebase team is not considering supporting edge anytime soon, and I feel Firebase is big enough provider we should make sure to provide at least enough guidance to folks using it in production with Next.js (ideally without third party packages). A simple way to opt-out of Edge if we are self hosted, as @arackaf suggested, would be enough for us, as our middleware already runs in Node.js anyway. |
Beta Was this translation helpful? Give feedback.
-
I'm guessing the primary workaround for this, then, is to move crypto-necessary tasks (and the like) into each router handler, right? |
Beta Was this translation helpful? Give feedback.
-
Still no word from Vercel on this? Is it on their roadmap? It's very frustrating for those of us that will be self-hosting on AWS and other solutions that are not Vercel to have middleware limited to Edge runtimes. As someone above mentioned, it really does feel a bit like vendor lock-in on the part of Vercel. It's hard to know without some kind of response from the team. In our case we want to use openid-client for checking and refreshing access tokens from httpOnly cookies. Middleware is the obvious place to do this, but it would seem that the only current solution is to query an API route from our middleware. I really hope they add an experimental flag that allows us to select the runtime for middleware. |
Beta Was this translation helpful? Give feedback.
-
please make this happens, it's really crucial for some applications that really need to use other than edge-runtime-provided module like bcrypt in their middleware, i hope nextjs team would read this |
Beta Was this translation helpful? Give feedback.
-
We're continuing to evaluate the constraint for Middleware using the Edge Runtime and exploring ways to bridge the gap towards allowing folks to experimentally/"dangerously" opt-into using the Node.js Runtime. I appreciate the feedback here, but let's assume good intent and not make broad generalizations. Let me add a bit more clarity here.
We agree. While the Edge Runtime has good constraints, for code that should run potentially in front of every request in your application, those constraints also have the tradeoff of smaller compatibility with existing ecosystem libraries. While there's been a lot of progress here in the past few years since we added Middleware, there's still libraries (as well as internal packages) that do not (or will not) support running with the Edge Runtime. We hear you.
To further underscore this, it is a key design constraint of Middleware today.
Correct, since this discussion was opened, Next.js Edge Runtime now supports:
It's also worth mentioning that another path forward here today is using Server Components with the App Router. You can add async logic, like handling authentication, in the root layout which applies to all routes. This is similar to Middleware in some cases. Further, you can add that logic in a nested layout, or a route group, only applying the logic to a specific set of routes. |
Beta Was this translation helpful? Give feedback.
This comment was marked as off-topic.
This comment was marked as off-topic.
-
Hi all, I just wanted to share a quick update that we're looking at this particular problem and will share more about it later as we start iterating on it. |
Beta Was this translation helpful? Give feedback.
-
So we ended up replacing the whole nextjs middleware call in sandbox.js with a plain function. That means any update has to be checked first but it was actually so simple to do that it shouldnt be an issue. Also patched out the waitUntil promises since we dont need it. Our middleware is basically equivalent to example below, I don't think others are needing much more complex stuff either. Read cookies, redirect, set headers etc. normal things. After all middleware function are very simple. In our case it's only to decide whether redirect or pass. At this rate it seems writing our node middleware sandbox is the easiest solution. Which only needs to be a thin wrapper to satisfy the response types the framework expects. |
Beta Was this translation helpful? Give feedback.
-
As a quick update, as someone shared in this thread, we've been exploring route-level middleware. The significant design constraint is how this fits with partial pre-rendering and streaming. An example of a pattern that would emerge for example is:
This is the ideal scenario we have in mind for performance. Now, I assume this does not solve some of the concerns in this thread. Please answer in the replies: 1) if that proposal sounds good to you and 2) if not, what blockers specifically have you encountered with the current middleware? what kind of logic are you running? My understanding is there are a few buckets. I have a few questions for each of those.
|
Beta Was this translation helpful? Give feedback.
-
A somewhat straightforward solution would be to add config option like useNodeMiddleware and then during server startup just import fhe function from "middleware.node" or whatever the convention would be. Then instead of the next middleware just run it and continue with the result. Looking at it the middleware result is just { response, waitUntil, fetchMetrics? } so whatever function works as long as the type matches. In our case the need is basically redirect/set cookie/pass, nothing fancy or messing around with RSC and other 3-letter abbreviations. I was messing around with the server and just patched the middleware execution to run an imported function. Basically there is a "blank" middleware.ts with matcher but instead of executing that in the sandbox it's just a function call. Also I noted that on "next build" the regular middleware was 27kB so using a plain function should improve performance a lot. After all there's no need to any fancy bundling etc. since node can just import whats's necessary on startup. Of course that's not a way to run production but I'd expect this to be easy to support officially. Another thing, I'm a bit concerned with global DB pooling and such, making sure the same pool is used everywhere after all the bundling etc. |
Beta Was this translation helpful? Give feedback.
-
https://github.com/vercel/next.js/releases/tag/v14.2.7 new update! edge middleware upgrade! still cannot switch to node |
Beta Was this translation helpful? Give feedback.
-
We always run our stuff on our servers and js stuff runs on nodejs and thus nodejs is always there. So this edge-runtime junkware limitations make no sense at all. All students tried to implement a simple app using Auth0/Prisma/MySQL to have a simple login/logout/register/app page (sessions) failed (having ~6 month for it). So obviously nextjs is everything else but simple and one needs to waste a lot of time to get even the usually simple things [not] fixed. Everything we saw so far suggests: reject any work/implementations, which requires/uses NextJS. So NexTJS should really add a config option to tell it: your are running on a real server [in a NodeJS environment and not a very limited useless platform like vercel's]. |
Beta Was this translation helpful? Give feedback.
-
even on new Next Js 15, we still don't have option for that, sad but i think they made it so we are locking into vercel :( |
Beta Was this translation helpful? Give feedback.
-
here is my work around for now, i will using a route groups with layout.tsx on each route groups. because i was using drizzle orm with mysql2 i cannot used it on the middleware because there's no 'net' module on the edge runtime.
layout.tsx import { auth } from "@/auth"
import { jakartaSans } from "@/lib/utils"
import { Children } from "@/types"
import { redirect } from "next/navigation"
const ProtectedLayout = async ({ children }: Children) => {
const session = await auth()
if (!session) redirect("/sign-in")
return (
<html lang="en">
<body className={jakartaSans.className}>
{children}
</body>
</html>
)
}
export default ProtectedLayout and if you want to get the current URL like import { auth } from "@/auth"
import { jakartaSans } from "@/lib/utils"
import { Children } from "@/types"
import { headers } from "next/headers"
import { redirect } from "next/navigation"
const AuthLayout = async ({ children }: Children) => {
const currentPath = new URL(headers().get("referer") || '').pathname
console.log("CURRENT PATH : ", currentPath)
//TODO: do something some logic based on the current path.
return (
<html lang="en">
<body className={jakartaSans.className}>
{children}
</body>
</html>
)
}
export default AuthLayout This is what i do for now, for authenticate user using Auth.js and sorry if there's something wrong, my english is not really good heheh :) |
Beta Was this translation helpful? Give feedback.
-
This is one of the issues that points to Next.JS being administered to favor Vercel's product, instead of being a React Framework, the fact that this is open since March 2023, and even before that people were questioning the removal of route based middleware(like express does) and forcing the Edge runtime. |
Beta Was this translation helpful? Give feedback.
-
Update (Request Interceptors)@unstubbable has a PR open for an experimental feature called Request Interceptors, which address some of the concerns in this discussion: If you have looked for anything brought up in this discussion, would be good to look at Request Interceptors to see if this helps your situation at all. Request Interceptors are a limited design, and Node.js Middleware is also in planning. |
Beta Was this translation helpful? Give feedback.
-
Update (Node.js Middleware also in development)As @feedthejim mentions below, aside from Request Interceptors mentioned above, the Next.js team is also working on planning for Node.js Middleware too:
|
Beta Was this translation helpful? Give feedback.
-
I share the frustration many in here feel, especially when it comes to auth and securing routes with middleware. Something i havent read here is the possibility of starting nextjs with a custom server. The docs provide a guide https://nextjs.org/docs/canary/app/building-your-application/configuring/custom-server if you use the standalone output mode, you can overwrite it with your custom one. app.prepare().then(() => {
const server = createServer((req, res) => {
const parsedUrl = parse(req.url!, true);
if (parsedUrl.pathname === "/secure") {
// check if session from req.cookies is in redis - or any middleware
res.writeHead(302, { location: "/test" });
res.end();
}
handle(req, res, parsedUrl);
});
server.listen(port);
console.log(`listening at http://localhost:${port}`);
}); As mentioned in the docs: this also comes with some caveats and is also just a workaround. |
Beta Was this translation helpful? Give feedback.
-
Please let us run node apis in middleware. Locking the middleware behind edge runtime doesn't make sense for self-hosting. I'm seeing a lot of positive changes in the framework in the last few months for users who are self-hosting. Please please please let us toggle the runtime in the middleware to run node apis. In one of my projects I just need to query redis in the same datacenter in the middleware for redirect decisions. It is a super high-traffic web app, and I'm now forced to build a route handler and invoke it in the middleware to get the result, which is terrible for TTFB because of having to make a network request in the middleware. Please at least give an experimental flag to run node apis in the middleware. |
Beta Was this translation helpful? Give feedback.
-
We are working on allowing using the Node.js runtime for Middleware, and it will be included in an upcoming Next.js release. We appreciate your feedback and patience on this issue. We are also exploring a potential new API, called Request Interceptors, that would address some of the feedback here for supporting nested, file-system based middleware. If this is something you are interested in, please comment on the proposal. To prevent additional GitHub notifications and emails, we have a new discussion where you can opt-into notifications as progress is made. Thank you 🙏 |
Beta Was this translation helpful? Give feedback.
We are working on allowing using the Node.js runtime for Middleware, and it will be included in an upcoming Next.js release. We appreciate your feedback and patience on this issue.
We are also exploring a potential new API, called Request Interceptors, that would address some of the feedback here for supporting nested, file-system based middleware. If this is something you are interested in, please comment on the proposal.
To prevent additional GitHub notifications and emails, we have a new discussion where you can opt-into notifications as progress is made.
Thank you 🙏