How to apply middleware based on a route group?
Unanswered
Golden-winged Warbler posted this in #help-forum
Golden-winged WarblerOP
With the unified middleware.js, how can I make decisions based on the route group?
I have not found a way based on the docs yet
I have not found a way based on the docs yet
59 Replies
@Golden-winged Warbler With the unified middleware.js, how can I make decisions based on the route group?
I have not found a way based on the docs yet
import { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith("/hello")) doThis();
else doThat();
}
Golden-winged WarblerOP
I realized later that groups have to have a unique path. I thought that I could have
be separate layouts and content, but apparently this is not allowed
(user)/dashboard
(admin)/dashboard
be separate layouts and content, but apparently this is not allowed
Golden-winged WarblerOP
The problem with a unified middleware, is when I need to have combinations of settings, which leads to nested if/switch statements per set
{A,B} x {C,D}
with distributed middleware, those conditions are also distributed.
How would your example work for slugs? At what point am I having to know to much about how the content is organized in middleware? There is an unnatural coupling of the middleware to how the routing tree is organized.
For example, if I want to move content to another route, I just move the files on disk, and the layout moves along with it. I would then have to go modify my middleware to account for any path checks and the conditional complexity therein with other middleware
{A,B} x {C,D}
with distributed middleware, those conditions are also distributed.
How would your example work for slugs? At what point am I having to know to much about how the content is organized in middleware? There is an unnatural coupling of the middleware to how the routing tree is organized.
For example, if I want to move content to another route, I just move the files on disk, and the layout moves along with it. I would then have to go modify my middleware to account for any path checks and the conditional complexity therein with other middleware
It just seems like the middleware is the oddball given the rest of the system design, unified function vs wrapping/nesting
well, they used to have nested middleware but it has been discontinued, so you have to work around it. there is no nested middleware and we cannot do anything about that
Golden-winged WarblerOP
they changed it once, they can change it again. I'm trying to understand the rationales so that I can make a good arguments for supporting nesting without going back to what didn't work before
good luck with that; i want nested middleware as well, but i'm not one to make decision
American black bear
+1 for nested middleware. It feels like app router is taking away a lot of control over the actual HTTP part of the web server, which makes a lot of things really clunky to deal with. Server components seem to have the capability to do things that middleware can do (they can send not found or do redirects), but everything that isn't within the allowed set of actions is locked away.
Middleware can do everything, but there isn't a good way to pass data from it to server components, and lacking nesting makes it hard to scale.
I feel like I can do most of what I want with server components, albeit with a lot of clunky re-writing and a lot of overhead. What I really want is a way to run code on a request, and use it to figure out what to do next (i.e. proper middleware, or what
Middleware can do everything, but there isn't a good way to pass data from it to server components, and lacking nesting makes it hard to scale.
I feel like I can do most of what I want with server components, albeit with a lot of clunky re-writing and a lot of overhead. What I really want is a way to run code on a request, and use it to figure out what to do next (i.e. proper middleware, or what
getServerSideProps
did).Golden-winged WarblerOP
agree, what we need is a hybrid setup, so that the API section does not have to be limited by the restrictions that the pages & react have
Ideally though, middleware could add context to the request processing as it is routed and passed through the middleware / routing onion [or tree, depending on how hungry you are :]
one could see a similar patter for {route.ts,middleware.ts} that we see for {page.tsx,layout.tsx}
middleware is a lot like providers, but for the API, so the distributed wrapping|nesting makes a lot of sense
American black bear
Actually, it looks like NextJS middleware only runs in the edge runtime (i.e. you can't use NodeJS APIs, in my case I can't connect to Redis in order to enforce ratelimits / handle sessions). I'm not sure if that's something you're using middleware for, but it seems to me like the "simplest" way to gain access to the underlying HTTP is to use a custom server.
Golden-winged WarblerOP
wow... I had to try it to believe it
why...
why can it not accept the
export const runtime = '...';
? (it currently excepts node... 🤣 )@Golden-winged Warbler why...
American black bear
No idea. I thought I could get away with moving everything that doesn't need to write headers (session is a stretch, but my frontend won't update the session), but it turns out you can't do crypto in the edge environment, so I just can't generate CSRF tokens securely 🙃
I'll probably just stick with page router until NodeJS isn't a second-class runtime anymore (or commit to using the custom server, which adds a lot more extra complexity than I really want to deal with). It seems a bit silly that I have to content with so many issues in order to get ratelimits, sessions, and CSRF working, but at least the edge runtime lets me shave 50ms off of each request 😆.
I wanted to use app router because I render some of my pages using markdown, but the markdown is static so the markdown renderer really only needs to live on the server. It'll cut down bandwidth and stop sending duplicate data, but I don't think it's worth dealing with all of this.
American black bear
That isn't a perfect solution, but would make things annoying instead of basically impossible.
I'd still love to see a proper system for handling middleware (or really, server-side-only code). Right now it's basically impossible to do anything, but even with access to req/res, it'd still be a bit painful without things like nesting and sending arbitrary (not just string) data between middeware and server components.
Golden-winged WarblerOP
I'm coming back to frontend after several years away, Next is the first recommendation when getting started with React. That's why I'm trying out Next, that and it has appeal despite the rough "edges"
@Golden-winged Warbler The problem with a unified middleware, is when I need to have combinations of settings, which leads to nested if/switch statements per set
{A,B} x {C,D}
with distributed middleware, those conditions are also distributed.
How would your example work for slugs? At what point am I having to know to much about how the content is organized in middleware? There is an unnatural coupling of the middleware to how the routing tree is organized.
For example, if I want to move content to another route, I just move the files on disk, and the layout moves along with it. I would then have to go modify my middleware to account for any path checks and the conditional complexity therein with other middleware
you can use next-connect to distribute your conditions. it works like express and is edge compatible
i agree that a unified middleware is not very good, but it is what it is and for now, we just have to deal with it
i agree that a unified middleware is not very good, but it is what it is and for now, we just have to deal with it
you'd still need to change the path strings everytime you rename or move files around
American black bear
Oops, forgot about that. +1 to next-connect. I forgot about it as I'm using it the wrong way 😆
Golden-winged WarblerOP
Yea, +1 on that @limwa this is the pattern I was thinking would be the work around, glad there is a library for it
glad i was able to help!
@Golden-winged Warbler I'm coming back to frontend after several years away, Next is the first recommendation when getting started with React. That's why I'm trying out Next, that and it has appeal despite the rough "edges"
American black bear
NextJS is wonderful... for page router. I'm willing to deal with some of the rough edges as app directory is brand new, and I do really hope that some of the issues clear up, but we'll see ¯_(ツ)_/¯
Golden-winged WarblerOP
The tediousness of having to put it everywhere won't be a problem because of our code gen (https://docs.hofstadter.io/getting-started/)
American black bear
In all honesty, most of my issues with app router come from trying to implement security features (which should be supported, but aren't needed everywhere). From my experience with it, app router seems great for everything else.
@American black bear In all honesty, most of my issues with app router come from trying to implement security features (which should be supported, but aren't needed everywhere). From my experience with it, app router seems great for everything else.
that's my take as well
one of the things that is probably the most annoying to me is that calling notFound() on a layout doesn't stop the child layouts or pages from rendering
i get that they're doing that to speed up the rendering by doing it all at once, but it'd be nice if there was a function that would run before everything else and sequentially for every file in the tree (maybe there is and i'm just unaware of it)
one of the things that is probably the most annoying to me is that calling notFound() on a layout doesn't stop the child layouts or pages from rendering
i get that they're doing that to speed up the rendering by doing it all at once, but it'd be nice if there was a function that would run before everything else and sequentially for every file in the tree (maybe there is and i'm just unaware of it)
Golden-winged WarblerOP
They list the processing order here: https://nextjs.org/docs/app/building-your-application/routing/middleware#matching-paths
@limwa that's my take as well
one of the things that is probably the most annoying to me is that calling notFound() on a layout doesn't stop the child layouts or pages from rendering
i get that they're doing that to speed up the rendering by doing it all at once, but it'd be nice if there was a function that would run before everything else and sequentially for every file in the tree (maybe there is and i'm just unaware of it)
American black bear
I haven't messed around with layouts too much. I thought
notFound
just threw an error. They can't cancel them all on an error?Golden-winged WarblerOP
notFound for a page or a component data?
American black bear
Unrelated, but the whole idea of
Is the pattern that seems to come up with it, and feels like a recipe for disaster when early exits aren't explicit.
notFound()
makes me really uncomfortable.if(/* is user not an admin? */) {
notFound();
}
// get data that only admins are allowed to
// see and render the page.
Is the pattern that seems to come up with it, and feels like a recipe for disaster when early exits aren't explicit.
Golden-winged WarblerOP
you ought to be able to return a 401 there, no?
American black bear
Guess what isn't possible 🙃
Golden-winged WarblerOP
like, you don't have to use notFound
American black bear
Pretend that app router allows us to send 401s. My main gripe is with the whole idea of omitting the return. Even if it technically isn't needed, it feels like it can easily lead to a slip up that causes a data leak of some sort.
it does throw an error, I dont know if it cancels the ones that are running or waits for them to finish
what i was trying to say is that if you have db queries in the children layouts for instance, they're probably going to be sent to the db
what i was trying to say is that if you have db queries in the children layouts for instance, they're probably going to be sent to the db
yeah, it also leads to some weird DX, especially with server actions
my problem with notFound, redirect, revalidatePath and revalidateTag throwing errors is that server actions then are not able to return data except if you're using cookies
@limwa it does throw an error, I dont know if it cancels the ones that are running or waits for them to finish
what i was trying to say is that if you have db queries in the children layouts for instance, they're probably going to be sent to the db
American black bear
Yeah, that's even worse then. At least in my example, the rest of the stuff wont run. I just don't like being overzealous with guards, but the framework does so much magic that I can't tell if something is secure to do or not.
Golden-winged WarblerOP
isn't the point to not return data and instead render on the server and just send the html?
if you want to send data, you are client side calling a traditional API, no? (with react-query and manual suspense?)
@Golden-winged Warbler isn't the point to not return data and instead render on the server and just send the html?
it depends on what you're trying to do
in my case, i don't have any API routes and just use server actions altogether. The reason for that is a bit silly but essentially, trpc was crashing my TS server all the time and server actions felt like a good DX, plus the advantage of being able to call revalidatePath and all the magic happens behind the scenes felt really appealing at the time
in my case, i don't have any API routes and just use server actions altogether. The reason for that is a bit silly but essentially, trpc was crashing my TS server all the time and server actions felt like a good DX, plus the advantage of being able to call revalidatePath and all the magic happens behind the scenes felt really appealing at the time
@limwa my problem with notFound, redirect, revalidatePath and revalidateTag throwing errors is that server actions then are not able to return data except if you're using cookies
American black bear
How's this work? I've stayed away from server actions (another thing that scares me with how magical and seemingly-insecure they are), so I don't know anything about them. I just saw the example on the documentation:
Which just looks like an unguarded write to a DB.
import { cookies } from 'next/headers'
// Server action defined inside a Server Component
export default function AddToCart({ productId }) {
async function addItem(data) {
'use server'
const cartId = cookies().get('cartId')?.value
await saveToDb({ cartId, data })
}
return (
<form action={addItem}>
<button type="submit">Add to Cart</button>
</form>
)
}
Which just looks like an unguarded write to a DB.
but for instance, if you're to give some feedback to the user about what happened in the server action, you'd need to return data and that is just really hard if you also want to use redirect or revalidatePath
imo, it would be much better if those functions were void and just marked some state in the response that would be received by the browser and then take the appropriate steps
American black bear
Oh. That sounds dreadful to deal with. What happens if you run a
notFound
inside of a server action?I love the idea of them in principle, but they just seem like another thing that would make me more likely to slip up. I write frontend code with a different "security mindset" than backend (data created on the frontend is trusted on the frontend, but frontend -> backend and backend -> frontend definitely isn't trusted), so mixing them together makes me feel like I'll accidentally see the server action (inside of my frontend code) as frontend code as well and write it insecurely.
@American black bear How's this work? I've stayed away from server actions (another thing that scares me with how magical and seemingly-insecure they are), so I don't know anything about them. I just saw the example on the documentation:
js
import { cookies } from 'next/headers'
// Server action defined inside a Server Component
export default function AddToCart({ productId }) {
async function addItem(data) {
'use server'
const cartId = cookies().get('cartId')?.value
await saveToDb({ cartId, data })
}
return (
<form action={addItem}>
<button type="submit">Add to Cart</button>
</form>
)
}
Which just looks like an unguarded write to a DB.
the idea is that the addItem function goes to the server bundle and receives as arguments the form data
then, all of the values that are used inside the action that come from it's "outer scope", are added to the form as serialized hidden inputs (imagine if you wanted to use the productId inside the action)
when the form is submitted, the browser (with no JS), sends a multipart form data request to the action endpoint
then, all of the values that are used inside the action that come from it's "outer scope", are added to the form as serialized hidden inputs (imagine if you wanted to use the productId inside the action)
when the form is submitted, the browser (with no JS), sends a multipart form data request to the action endpoint
with JS enabled on the browser, things are a bit more complicated on the client, but on the server, things are pretty much the same
one cool thing about server actions is that, if you perform a redirect or revalidatePath inside of them, they also send in the response the updated UI state, which means that your browser doesn't need to do another request to update the UI
you should always validate the input of your server actions
anyways, we're a bit off topic for this thread, @Golden-winged Warbler if you feel like an appropriate solution to your problem was found, please mark the answer with the mark solution option