Skip to content

Instantly share code, notes, and snippets.

@augiwan
Created October 24, 2025 09:22
Show Gist options
  • Select an option

  • Save augiwan/95f533556fab39443119a36466f7d32d to your computer and use it in GitHub Desktop.

Select an option

Save augiwan/95f533556fab39443119a36466f7d32d to your computer and use it in GitHub Desktop.
Fixes better-t-stack orpc implementation
diff --git a/apps/web/src/utils/orpc.ts b/apps/web/src/utils/orpc.ts
index d369d41..c5dc202 100644
--- a/apps/web/src/utils/orpc.ts
+++ b/apps/web/src/utils/orpc.ts
@@ -1,13 +1,11 @@
import { createORPCClient } from "@orpc/client";
import { RPCLink } from "@orpc/client/fetch";
import { createTanstackQueryUtils } from "@orpc/tanstack-query";
-import { QueryCache, QueryClient } from "@tanstack/react-query";
-import { toast } from "sonner";
-import { createRouterClient } from "@orpc/server";
import type { RouterClient } from "@orpc/server";
+import type { AppRouter } from "@myapp/api/routers/index";
import { createIsomorphicFn } from "@tanstack/react-start";
-import { appRouter } from "@myapp/api/routers/index";
-import { createContext } from "@myapp/api/context";
+import { QueryCache, QueryClient } from "@tanstack/react-query";
+import { toast } from "sonner";
export const queryClient = new QueryClient({
queryCache: new QueryCache({
@@ -25,14 +23,27 @@ export const queryClient = new QueryClient({
});
const getORPCClient = createIsomorphicFn()
- .server(() =>
- createRouterClient(appRouter, {
- context: async ({ req }) => {
- return createContext({ req });
+ .server((ctx = {} as { req?: Request }): RouterClient<AppRouter> => {
+ const requestHeaders = ctx.req ? new Headers(ctx.req.headers) : undefined;
+
+ const link = new RPCLink({
+ url: `${import.meta.env.VITE_SERVER_URL}/rpc`,
+ fetch(url, options) {
+ const mergedHeaders = new Headers(options?.headers);
+ requestHeaders?.forEach((value, key) => {
+ mergedHeaders.set(key, value);
+ });
+ return fetch(url, {
+ ...options,
+ credentials: "include",
+ headers: mergedHeaders,
+ });
},
- }),
- )
- .client((): RouterClient<typeof appRouter> => {
+ });
+
+ return createORPCClient(link) as RouterClient<AppRouter>;
+ })
+ .client((): RouterClient<AppRouter> => {
const link = new RPCLink({
url: `${import.meta.env.VITE_SERVER_URL}/rpc`,
fetch(url, options) {
@@ -43,9 +54,9 @@ const getORPCClient = createIsomorphicFn()
},
});
- return createORPCClient(link);
+ return createORPCClient(link) as RouterClient<AppRouter>;
});
-export const client: RouterClient<typeof appRouter> = getORPCClient();
+export const client: RouterClient<AppRouter> = getORPCClient();
export const orpc = createTanstackQueryUtils(client);
@augiwan
Copy link
Author

augiwan commented Oct 24, 2025

Whats happening here:

  • Previously the server-side getORPCClient was directly instantiating an in-memory router client using

    createRouterClient(appRouter, { context: ... })

    This meant server code executed RPC handlers locally without making network calls.

  • The patch removes createRouterClient and appRouter imports and replaces the server logic with an actual RPCLink pointing to ${VITE_SERVER_URL}/rpc, same as the client side. Effectively, server RPC calls are no longer “short-circuited”; they now hit the Hono RPC endpoint just like the browser.

  • It propagates incoming request headers to the RPC call:

    const requestHeaders = ctx.req ? new Headers(ctx.req.headers) : undefined;

    Then merges them into the fetch call. Useful for forwarding authentication cookies, sessions, or other headers when doing SSR/loader calls.

  • Both client and server now use the same createORPCClient(link) setup, typed as RouterClient<AppRouter>. Type safety remains, but no longer requires importing the router directly on the frontend.

  • Context creation on the server (e.g. createContext({ req })) is removed here, since the actual API server will handle it.

  • The export const client typing is updated from

    RouterClient<typeof appRouter>

    to

    RouterClient<AppRouter>

    so the web app depends only on shared type definitions, not implementation.

  • Why this is better:

    • Cleaner separation between web and API server.
    • Enables hosting API independently (no local execution).
    • Easier to deploy edge/runtime environments.
    • Slight performance cost since server-side calls now incur network latency.
    • Easier to maintain — no more pulling API router and context into the frontend app.
  • A few points to keep in mind (but the current trpc implementation also does this):

    • SSR will now depend on the RPC server being reachable and performant.
    • Any logic previously relying on direct context access on the web server must now be handled via headers/session propagation.
    • If you were relying on createRouterClient for certain dev shortcuts, those are gone.

PS: Re-formatted with AI

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment