|
Hi all, I have server-side actions that should be rate limited. I can implement this with a web server (Hono) rate limiter, which returns 429 when triggered. This is the server-side behavior I wanted, but this approach causes the nearest ErrorBoundary to render. I gather this is because the rate limiter replies with a plain 429, whereas a I can work around the problem like this but it's terrible const limiter = rateLimiter<Env>({
keyGenerator: c => getConnInfo(c).remote.address ?? '',
handler: c =>
c.json([{ _1: 2 }, 'data', { _3: 4 }, 'message', 'too fast'], {
status: 429,
headers: {
'x-remix-response': 'yes'
}
})
})What is the intended solution? Route middleware? I could do that but I'd have to use a different rate limiter... |
Replies: 1 comment
|
The behaviour you hit is deliberate, and the source comment names your exact case.
// If this error'd without hitting the running server, then bubble a normal
// `ErrorResponse` and don't try to decode the body with `turbo-stream`.
//
// This could be triggered by a few scenarios:
// - `.data` request 404 on a pre-rendered app using a CDN
// - 429 error returned from a CDN on a SSR app
if (res.status >= 400 && !res.headers.has("X-Remix-Response")) {
throw new ErrorResponseImpl(res.status, res.statusText, await res.text());
}From the client's side a 429 from your Hono limiter is indistinguishable from a 429 from a CDN sitting in front of the app: it never reached the React Router handler, so there is no serialized result to hand a fetcher, and bubbling to the Which is also why I would not ship your workaround. That array isn't JSON with a funny shape — it's Middleware won't do what you want if you
So Returning is a different path from throwing. A middleware may skip // A type predicate function to check if the values returned by the user without
// a next() call is of the proper type. If so we use it directly, otherwise we
// call next() for them.
isResult: (v: unknown) => v is Result,return isDataWithResponseInit(result)
? dataWithResponseInitToResponse(result)
: result;So the shape to try is export const middleware: Route.MiddlewareFunction[] = [
async ({ request, context }) => {
if (await isRateLimited(request)) {
return data("too fast", { status: 429 }); // no next() call
}
},
];I have read the short-circuit and The path that is guaranteed to work today is the boring one: do the limiter check inside the |
The behaviour you hit is deliberate, and the source comment names your exact case.
packages/react-router/lib/dom/ssr/single-fetch.tsx:From the client's side a 429 from your Hono limiter is indistinguishable from a 429 from a CDN sitting in front …