Ask
148
@wren_oyelaran ·

middleware runs twice per navigation and my rate limiter 429s real users Middleware

Rate limiting in middleware.ts, 10 requests per minute per IP, matcher on /dashboard/:path*. Support tickets started coming in: people get a 429 after roughly five clicks around the dashboard.

Logged every invocation with the URL and headers. Every navigation produces two: one with ?_rsc= on the URL and a next-router-prefetch: 1 header, and one for the actual navigation. So my budget of 10 is really a budget of 5, and it gets worse on pages with several links in view.

Is the accepted fix to exclude prefetches, or is rate limiting in middleware just the wrong place?

8 answers Share
Report

Answering anonymously — a moderator will review it first.

  • @wrangler_dev_jo · 3w ago · 3 replies

    Both. Exclude prefetches now, then move the limiter.

    The matcher takes missing, so you can keep prefetch requests out entirely:

    export const config = {
      matcher: [{
        source: '/dashboard/:path*',
        missing: [{ type: 'header', key: 'next-router-prefetch' }]
      }]
    }
    

    But the deeper issue is that you are counting page views, and page views are not the thing you are protecting. Nobody abuses your app by looking at the dashboard. Put the limiter on the route handlers and server actions that write or that call something expensive, keyed on the user id rather than the IP, and let navigation be free. Your office customers behind one NAT will thank you too.

    44
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @wren_oyelaran · 2w ago

      Matcher change shipped this morning, ticket volume went to zero. Moving the limiter to the mutation layer this week - you are right that IP keying was the other half of it, we had a customer with 30 people on one office IP.

      13
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
    • @arrayformula_al · 2w ago

      When you move it, add a test that fires the same action 12 times and asserts the 11th is rejected. Rate limiters are the classic thing that silently stops working after a refactor because nobody ever exercises the unhappy path.

      9
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @lowtech_lior · 2w ago

    Middleware runs far more often than people picture. Prefetches, RSC payload requests, and anything else your matcher happens to catch. I have seen a matcher of /:path* with a lazy negative lookahead pull in every image request on the page, at which point your "10 per minute" is 10 per screenful.

    Log req.url for a day before you trust any assumption about how often it fires.

    28
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @costbasis_carl · 2w ago

    Also look at what this is costing you. On most hosts middleware invocations are billed separately from function invocations, and a chatty matcher on a dashboard with 40 links in the viewport is a genuinely surprising line item. I cut mine 60% with matcher changes alone and the app behaved identically.

    17
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @keg_line_ken · 2w ago · 2 replies

    Just put prefetch={false} on every Link in the sidebar. One prop, done, and you stop paying for prefetches you mostly do not use.

    12
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @awkward_ash · 2w ago

      It fixes the counter and breaks the app. Prefetching is why an internal dashboard feels instant; turn it off across the sidebar and every navigation now waits on a round trip you used to hide. Fix the matcher, keep the prefetch.

      8
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @epoxy_puddle · 3w ago

    "The framework is making requests on the user's behalf and your infrastructure cannot tell them apart from the user" is going to be the theme of the next few years, honestly.

    9
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report