Skip to content

Route matching loses case-insensitivity in built Workers bundle — new RegExp() calls missing flags #1221

Description

@TLSutherland

Summary

Routes compiled from next.config.ts's redirects() / rewrites() (and any other manifest-driven route matching) lose case-insensitivity when matched at runtime in the OpenNext-built bundle, even though the equivalent request is correctly matched case-insensitively by next dev / next start.

Root cause: every runtime route-matching call site in @opennextjs/aws constructs new RegExp(pattern) with no flags against routes-manifest.json entries. Next.js's own manifest-driven matching defaults to case-insensitive (caseSensitiveRoutes: false unless a project opts in), but nothing in @opennextjs/aws's matcher passes an i flag (or otherwise respects the manifest's case-sensitivity setting) when reconstructing these regexes.

Environment

  • @opennextjs/aws: 4.0.2 (confirmed still present in 4.1.0, the latest published version as of this report)
  • @opennextjs/cloudflare: 1.19.11
  • next: 16.2.6
  • Deployed target: Cloudflare Workers

Repro

  1. In next.config.ts, add a redirect:
    async redirects() {
      return [
        { source: '/mypartner', destination: '/learn/partners/mypartner', permanent: true },
      ];
    }
  2. Run next dev and request /MyPartner (or /MYPARTNER) → correctly redirects to /learn/partners/mypartner. Next's dev-time route matching is case-insensitive by default.
  3. Build with @opennextjs/cloudflare and deploy to Workers (or run the built Worker locally via wrangler dev).
  4. Request /MyPartner (or /MYPARTNER) against the deployed Worker → 404. Only the exact-case /mypartner redirects correctly.

Root cause detail

Confirmed directly against the published @opennextjs/aws@4.1.0 npm package (dist/) — every runtime route-regex construction site builds the RegExp with no flags argument:

  • dist/core/util.js:110new RegExp(route.regex).test(rawPath ?? "")
  • dist/core/routing/matcher.js:80new RegExp(regex).test(path)
  • dist/core/routing/matcher.js:112new RegExp(route.regex).test(path)
  • dist/core/routing/matcher.js:304new RegExp(routeRegex)
  • dist/core/edgeFunctionHandler.js:17new RegExp(r) (per-route regex from globalThis._ROUTES)
  • dist/core/routing/cacheInterceptor.js:215new RegExp(dr.routeRegex).test(localizedPath)

None of these read or forward a case-sensitivity flag from the corresponding routes-manifest.json entry (or from next.config.ts's caseSensitiveRoutes setting). Compare with the flag-aware helper already present elsewhere in the same package, dist/utils/regex.js's getCrossPlatformPathRegex(regex, { flags = "" }) — which demonstrates the codebase already has a pattern for this, just not applied at the call sites above.

I checked the v4.0.2 → v4.1.0 diff on matcher.ts, cacheInterceptor.ts, and edgeFunctionHandler.ts (files that did change between those versions) — the changes are unrelated (atomic decodeURIComponent / malformed-path decode-safety, and an added decoded-path match variant in edgeFunctionHandler). None of them add a case-insensitivity flag.

Impact

Any project relying on next.config.ts's built-in redirects()/rewrites() case-insensitive matching (the Next.js default) will see that behavior silently work in next dev but break in the OpenNext/Cloudflare Workers build — no build-time warning, no type error, just a 404 in production for any mixed-case request that isn't an exact match. We only caught this because we noticed vanity-path redirects behaving inconsistently between local dev and the deployed Worker.

Workaround in use

We added an explicit case-folding layer in our own middleware.ts (lowercase the incoming path segment, look it up against a pre-extracted lowercase table, rewrite/redirect to the canonical lowercase path) to route around this, since we can't patch the vendored matcher. Happy to share the pattern if useful, but it only covers our own redirect table — any other next.config.ts project would hit the same bug with zero workaround unless they build the same kind of shim.

Suggested fix

Thread the manifest's case-sensitivity setting (or default to case-insensitive, matching Next's own default) through to each of the new RegExp(...) call sites listed above, the same way getCrossPlatformPathRegex already supports a flags option.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions