Skip to content

[BUG] Cloudflare build crashes on catch-all API route /api/auth/[...better-auth] due to invalid regex #942

@collinsedim

Description

@collinsedim

Describe the bug

Running opennextjs-cloudflare preview or opennextjs-cloudflare deploy on a Next.js App Router project that includes a catch-all API route at app/api/auth/[...better-auth]/route.ts causes the generated Cloudflare worker to fail at startup. The worker throws SyntaxError: Invalid regular expression: /^/api/auth/[...better-auth](?:/)?$/: Range out of order in character class, so Wrangler cannot start the service.

Steps to reproduce

  1. Create a Next.js App Router project (Next 15+) with app/api/auth/[...better-auth]/route.ts.
  2. Install @opennextjs/cloudflare@1.9.2 and build with opennextjs-cloudflare build.
  3. Run opennextjs-cloudflare preview (or deploy).
  4. Observe the worker crash above in the preview/deploy logs.

Expected behavior

The worker should start successfully, routing api/auth requests through Better Auth without crashing, and preview/deploy should complete normally.

@opennextjs/cloudflare version

1.9.2

Wrangler version

4.42.2

next info output

/bin/sh: 1: yarn: not found

Operating System:
  Platform: linux
  Arch: x64
  Version: #85-Ubuntu SMP PREEMPT_DYNAMIC Thu Sep 18 15:26:59 UTC 2025
  Available memory (MB): 7822
  Available CPU cores: 4
Binaries:
  Node: 24.2.0
  npm: 11.3.0
  Yarn: N/A
  pnpm: 10.15.0
Relevant Packages:
  next: 15.5.4 // Latest available version is detected (15.5.4).
  eslint-config-next: 15.5.4
  react: 19.1.0
  react-dom: 19.1.0
  typescript: 5.9.3
Next.js Config:
  output: N/A

Additional context

  • Versions tested: @opennextjs/cloudflare@1.9.2, next@15.5.4, better-auth@1.3.27, wrangler@4.42.2.
  • Root cause: getStaticAPIRoutes registers dynamic App Router API entries as static routes because the filter does not exclude items present in RoutesManifest.routes.dynamic. The resulting regex literal ^/api/auth/[...better-auth](?:/)?$ is invalid due to the unescaped hyphen.
  • Suggested fix:
    .filter((route) =>
      (route.startsWith("/api/") || route === "/api") &&
      !dynamicRoutePages.has(route)
    )
    This brings the filter in line with the intended behavior and prevents dynamic catch-all routes from being emitted as static regexes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingtriage

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions