Skip to content

Silent 500 on SSR: unguarded window.location.origin in @clerk/nextjs frontendApiProxy code path #8304

@Shorebirdmgmt

Description

@Shorebirdmgmt

Summary

@clerk/nextjs@7.1.0's compiled <ClerkProvider> SSR graph contains an unguarded reference to window.location.origin in the helper that builds the Clerk CDN URL from proxyUrl. When NEXT_PUBLIC_CLERK_PROXY_URL is set to a relative path (e.g. /__clerk, as recommended in the frontendApiProxy docs), server-side rendering on a Node-less runtime like Cloudflare Workers throws ReferenceError: window is not defined. Next.js middleware / OpenNext catch the throw and convert it to a plain 500 Response, so the failure is silent — no exception surfaces in wrangler tail.

This broke every route on our production website-admin worker after we enabled frontendApiProxy per the documented setup. The root cause took several hours of bundle forensics to find because the silent catch hid it from standard observability.

Evidence

Exact offending line, observed in a fresh next build of @clerk/nextjs@7.1.0 at .next/server/chunks/ssr/_0~zk87u._.js:18:

let aU = a => {
  var b;
  let { proxyUrl: c, domain: d, publishableKey: e } = a;
  return c && (!c || /^http(s)?:\/\//.test(b || "") || q(c))
    ? (!c ? "" : q(c) ? new URL(c, window.location.origin).toString() : c)
        .replace(/http(s)?:\/\//, "")
    : d && !aT((0, aN.u)(e)?.frontendApi || "")
      ? (0, aP.t)(d)
      : (0, aN.u)(e)?.frontendApi || "";
};

q(c) returns true for relative URLs. The result is new URL(c, window.location.origin) on the server — fatal in any SSR-without-DOM environment (Cloudflare Workers, Deno Deploy, bare Edge Runtime, any workerd-based host).

Reproduction

  1. Next.js 16.2.3 App Router project on @opennextjs/cloudflare@1.19.0, deployed to Cloudflare Workers.
  2. proxy.ts (Next 16 middleware) passes { frontendApiProxy: { enabled: true } } to clerkMiddleware().
  3. Set NEXT_PUBLIC_CLERK_PROXY_URL=/__clerk as a wrangler secret (or similar relative value at build time).
  4. Deploy. Hit any route rendering <ClerkProvider>. 500 returned, body Internal Server Error, wrangler tail shows "exceptions": [].

Clearing NEXT_PUBLIC_CLERK_PROXY_URL (empty string) restores the site because aU() short-circuits on falsy proxyUrl before reaching the window.location.origin branch.

Impact

  • Silent 500 on every SSR route. Zero observability in Cloudflare's standard logs (wrangler tail). The fact that Next's Edge runtime swallows the throw into a plain Response is what makes this hard to diagnose, but the bug itself is the unguarded window reference.
  • Affects anyone following Clerk's own frontendApiProxy documentation on a Workers-class host.
  • Version range at minimum: confirmed present in @clerk/nextjs@7.1.0. 7.0.12 (the version in our last known-good deploy) was not exercised with frontendApiProxy so I can't state whether it was present there. A git-bisect of packages/shared/src/client/clerk-script.tsx (or whichever source file compiles to aU()) should identify the introducing change.

Suggested fixes

Either would resolve it. We're currently parked on #2's manual mitigation and would like to restore the documented frontendApiProxy setup once an upstream fix lands.

  1. Guard the window.location.origin reference. Replace new URL(c, window.location.origin).toString() with a runtime-safe builder — on the server, derive origin from request headers (x-forwarded-host, x-forwarded-proto) or from NEXT_PUBLIC_CLERK_PROXY_URL's containing host if it's available at build/config time.
  2. Document the hazard. If a real fix isn't viable in the SDK, at minimum update the frontendApiProxy docs to warn that NEXT_PUBLIC_CLERK_PROXY_URL must be an absolute URL on Workers-class runtimes.

Environment

  • @clerk/nextjs@7.1.0
  • next@16.2.3
  • @opennextjs/cloudflare@1.19.0
  • wrangler@4.81.0
  • Cloudflare Workers (workerd), nodejs_compat + global_fetch_strictly_public compatibility flags
  • Production incident: recovered by deleting the NEXT_PUBLIC_CLERK_PROXY_URL wrangler secret and reverting to clerkMiddleware() without frontendApiProxy.

Related

We've documented the full forensic trail in our internal ADR. Happy to share additional reproducer details, compiled-bundle excerpts, or the rolled-back worker version ID if useful.

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