Skip to content

isDefinedError doesn't work when using globalThis.$client for SSR optimization in nextjs #957

Description

@roErlend

Environment

Nextjs 15.3.1
"@orpc/react": "^1.8.5",
"@orpc/server": "^1.8.5",

Reproduction

Can provide if it is deemed a bug, and not just a misunderstanding

Describe the bug

I'm following the documentation for setting up orpc in nextjs, and have also looked the example playground for nextjs.

orpc.ts:

const link = new RPCLink({
  url: `${env.NEXT_PUBLIC_APP_URL}/api/rpc`,
});

/**
 * Fallback to client-side client if server-side client is not available.
 */
export const orpcClient: RouterClient<typeof appRouter> = globalThis.$client ?? createORPCClient(link);

orpc.server.ts:

/**
 * This is part of the Optimize SSR setup.
 *
 * @see {@link https://orpc.unnoq.com/docs/adapters/next#optimize-ssr}
 */
globalThis.$client = createRouterClient(appRouter, {
  /**
   * Provide initial context if needed.
   *
   * Because this client instance is shared across all requests,
   * only include context that's safe to reuse globally.
   * For per-request context, use middleware context or pass a function as the initial context.
   */
  context: async () => ({
    headers: await headers(), // provide headers if initial context required
  }),
});
const { error, data, isDefined } = await safe(orpcClient.orderModule.powerPriceSummary());
  console.log("🚀 ~ TestPage ~ isDefined:", isDefined);
  console.log("🚀 ~ TestPage ~ data:", data);
  console.log("🚀 ~ TestPage ~ error:", error);
  if (isDefinedError(error) && error.code === "UNAUTHORIZED") {
    // Never reaches here
  }

I have defined UNAUTHORIZED as a base error that my middleware throws like this:

    if (!session?.accessToken) {
      throw errors.UNAUTHORIZED({ message: "You must be logged in to access this resource." });
    }

When looking at the logged error, it looks correct, and defined is true.
The problem is that isDefinedError returns false, along with isDefined from the safe function. The procedure is called in a server component, and so the globalThis.$client seems to be used. If I remove that part of the code so that the orpcClient is always the createORPCClient(link) variant, then it works, but I'm not willing to give up the SSR optimization in order to get type safe errors here.
Is this expected behavior? Is there another apporach I am expected to use? I couldn't find anything in the documentation regarding this, and everywhere I read about it, it seems like it's just expected to work

Additional context

No response

Logs

Logs from the example code

Server  🚀 ~ session: null authMiddleware.ts:13:13
Server  🚀 ~ TestPage ~ isDefined: false page.tsx:12:11
Server  🚀 ~ TestPage ~ data: undefined page.tsx:13:11
Server  🚀 ~ TestPage ~ error: Object { defined: true, code: "UNAUTHORIZED", status: 401, message: "You must be logged in to access this resource." }

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions