0

I was working on a nextjs monorepo using trpc as backend I am integrating SSE with normal trpc methods, I implemented auth using better auth and it works very smoothly, but when I try to prefetch on server components I get not authenticated user error, but after render the app works without error. I checked further and reached upon conclusion that cookies arent resolved because when I tried passing headers vis prop drilling from TRPC provider and using those in getting auth it worked and prefetching worked here is the code.

// init.ts
import { initTRPC, TRPCError } from '@trpc/server';
import superjson from "superjson";
import { cache } from 'react';
import { auth } from '../lib/auth';
import { headers as getHeaders } from 'next/headers';

export const createTRPCContext = cache(async () => { });

export type Context = Awaited<ReturnType<typeof createTRPCContext>>;

const t = initTRPC.context().create({
    transformer: superjson,
    errorFormatter({ shape }) {
        return shape;
    },
    sse: {
        maxDurationMs: 5 * 60 * 1_000,
        ping: {
            enabled: true,
            intervalMs: 3_000,
        },
        client: {
            reconnectAfterInactivityMs: 5_000,
        },
    },
});

export const createTRPCRouter = t.router;
export const createCallerFactory = t.createCallerFactory;
export const baseProcedure = t.procedure;
export const protectedProcedure = baseProcedure.use(async ({ ctx, next }) => {
    const headers = await getHeaders();
    const session = await auth.api.getSession({
        headers
    });

    if (!session || !session.user) {
        throw new TRPCError({
            code: "UNAUTHORIZED",
            message: "You must be logged in to access this resource",
        });
    }

    return next({
        ctx: {
            ...ctx,
            session,
        },
    });
});
// server.tsx
import 'server-only';

import { createTRPCOptionsProxy, TRPCQueryOptions } from '@trpc/tanstack-react-query';
import { cache } from 'react';
import { makeQueryClient } from './query-client';
import { appRouter } from './routers/_app';
import { dehydrate, HydrationBoundary } from '@tanstack/react-query';
import { createTRPCContext } from './init';

export const getQueryClient = cache(makeQueryClient);

export const trpc = createTRPCOptionsProxy({
    ctx: createTRPCContext,
    router: appRouter,
    queryClient: getQueryClient,
});

export function HydrateClient(props: { children: React.ReactNode }) {
    const queryClient = getQueryClient();
    return (
        <HydrationBoundary state={dehydrate(queryClient)}>
            {props.children}
        </HydrationBoundary>
    );
}

export function prefetch<T extends ReturnType<TRPCQueryOptions<any>>>(
    queryOptions: T,
) {
    const queryClient = getQueryClient();
    if (queryOptions.queryKey[1]?.type === 'infinite') {
        void queryClient.prefetchInfiniteQuery(queryOptions as any);
    } else {
        void queryClient.prefetchQuery(queryOptions);
    }
}
// route.ts
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { appRouter } from "@workspace/trpc/server/routers/_app";
import { createTRPCContext } from "@workspace/trpc/server/init";

const handler = async (req: Request) => {
    return fetchRequestHandler({
        endpoint: "/api/trpc",
        req,
        router: appRouter,
        createContext: createTRPCContext,
    });
};

export { handler as GET, handler as POST };
// client.tsx
"use client";

import type { QueryClient } from "@tanstack/react-query";
import { QueryClientProvider } from "@tanstack/react-query";
import {
    createTRPCClient,
    httpSubscriptionLink,
    httpBatchLink,
    httpLink,
    splitLink,
    loggerLink,
    isNonJsonSerializable,
    type TRPCLink,
} from "@trpc/client";
import { createTRPCContext } from "@trpc/tanstack-react-query";
import { useState } from "react";
import { makeQueryClient } from "@workspace/trpc/server/query-client";
import type { AppRouter } from "@workspace/trpc/server/routers/_app";
import superjson from "superjson";

export const { TRPCProvider, useTRPC } = createTRPCContext<AppRouter>();

let browserQueryClient: QueryClient;

function getQueryClient(): QueryClient {
    if (typeof window === "undefined") return makeQueryClient();
    if (!browserQueryClient) browserQueryClient = makeQueryClient();
    return browserQueryClient;
}

function getUrl() {
    const base = (() => {
        if (typeof window !== "undefined") return "";
        return process.env.NEXT_PUBLIC_APP_URL!;
    })();
    return `${base}/api/trpc`;
}

export function TRPCReactProvider(props: {
    children: React.ReactNode;
}) {
    const queryClient = getQueryClient();

    const [trpcClient] = useState(() => {
        const httpLinkSingleInstance = httpLink({
            url: getUrl(),
            transformer: superjson,
            fetch(url, options) {
                return fetch(url, {
                    ...options,
                    credentials: "include",
                });
            },
        });

        const httpBatchLinkInstance = httpBatchLink({
            url: getUrl(),
            transformer: superjson,
            fetch(url, options) {
                return fetch(url, {
                    ...options,
                    credentials: "include",
                });
            },
        });

        const httpSubscriptionLinkInstance = httpSubscriptionLink({
            url: getUrl(),
            transformer: superjson,
        });

        const links: TRPCLink<AppRouter>[] = [
            loggerLink(),
            splitLink({
                condition: (op) => op.type === "subscription",
                true: httpSubscriptionLinkInstance,
                false: splitLink({
                    condition: (op) => isNonJsonSerializable(op.input),
                    true: httpLinkSingleInstance,
                    false: httpBatchLinkInstance,
                }),
            }),
        ];

        return createTRPCClient<AppRouter>({ links });
    });

    return (
        <QueryClientProvider client={queryClient}>
            <TRPCProvider trpcClient={trpcClient} queryClient={queryClient}>
                {props.children}
            </TRPCProvider>
        </QueryClientProvider>
    );
}
// layout where I am prefetching
import { requireConfiguredAccount } from "@/lib/auth-utils";
import { agentParamsLoader } from "@/modules/agent/server/params-loader";
import { AgentLayout } from "@/modules/agent/ui/layouts/agent-layout";
import { prefetch, trpc } from "@workspace/trpc/server/server";
import type { SearchParams } from "nuqs";

type Props = {
    searchParams: Promise<SearchParams>
    children: React.ReactNode;
}

const Layout = async ({ children, searchParams }: Props) => {
    const { user } = await requireConfiguredAccount();
    const params = await agentParamsLoader(searchParams);

    prefetch(
        trpc.notifications.getUnreadCount.queryOptions()
    );
    prefetch(
        trpc.conversations.getMany.infiniteQueryOptions(params)
    );
    return (
        <AgentLayout image={user.image} name={user.name}>
            {children}
        </AgentLayout>
    )
}

export default Layout;
// auth.ts
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { prisma } from "@workspace/db";
import { sendEmailVerification, sendEmailVerified, sendPasswordReset, sendPasswordResetSuccess } from "../server/modules/internal/mails";

export const auth = betterAuth({
    database: prismaAdapter(prisma, {
        provider: "postgresql",
    }),
    emailAndPassword: {
        enabled: true,
        requireEmailVerification: false,
        autoSignIn: true,
        sendResetPassword: async ({ user, url }) => {
            await sendPasswordReset(user.email, url);
        },
        onPasswordReset: async ({ user }) => {
            await sendPasswordResetSuccess(user.email);
        },
    },
    socialProviders: {
        google: {
            clientId: process.env.GOOGLE_CLIENT_ID!,
            clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
        },
    },
    emailVerification: {
        sendOnSignUp: true,
        autoSignInAfterVerification: false,
        sendVerificationEmail: async ({ user, url }) => {
            await sendEmailVerification(user.email, url);
        },
        onEmailVerification: async (user) => {
            await sendEmailVerified(user.email)
        },
    }
});

now the error

 Compiled /api/trpc/[trpc] in 3.7s
 GET /api/trpc/notifications.getUnreadCount?batch=1&input=%7B%220%22%3A%7B%22json%22%3Anull%2C%22meta%22%3A%7B%22values%22%3A%5B%22undefined%22%5D%2C%22v%22%3A1%7D%7D%7D 401 in 8238ms
 << query  #1 notifications.getUnreadCount  {
  input: undefined,
  result: [Error [TRPCClientError]: You must be logged in to access this resource] {
    cause: undefined,
    shape: {
      message: 'You must be logged in to access this resource',
      code: -32001,
      data: [Object]
    },
    data: {
      code: 'UNAUTHORIZED',
      httpStatus: 401,
      stack: 'TRPCError: You must be logged in to access this resource\n' +
        '    at C:\\Users\\rites\\Desktop\\pathway\\pathway\\apps\\web\\.next\\server\\chunks\\[root-of-the-server]__5b5a3eb5._.js:1244:15\n' +
        '    at async callRecursive (C:\\Users\\rites\\Desktop\\pathway\\pathway\\apps\\web\\.next\\server\\chunks\\a30f9_@trpc_server_dist_18a3987f._.js:3857:24)\n' +
        '    at async procedure (C:\\Users\\rites\\Desktop\\pathway\\pathway\\apps\\web\\.next\\server\\chunks\\a30f9_@trpc_server_dist_18a3987f._.js:3882:24)\n' +
        '    at async C:\\Users\\rites\\Desktop\\pathway\\pathway\\apps\\web\\.next\\server\\chunks\\a30f9_@trpc_server_dist_18a3987f._.js:3189:30\n' +
        '    at async Promise.all (index 0)\n' +
        '    at async resolveResponse (C:\\Users\\rites\\Desktop\\pathway\\pathway\\apps\\web\\.next\\server\\chunks\\a30f9_@trpc_server_dist_18a3987f._.js:3403:26)\n' +
        '    at async fetchRequestHandler (C:\\Users\\rites\\Desktop\\pathway\\pathway\\apps\\web\\.next\\server\\chunks\\a30f9_@trpc_server_dist_18a3987f._.js:3511:12)\n' +
        '    at async AppRouteRouteModule.do (C:\\Users\\rites\\Desktop\\pathway\\pathway\\node_modules\\.pnpm\\[email protected]_@opentelemetry+_f94025e8e3d705ec55e3c88f5d76a316\\node_modules\\next\\dist\\compiled\\next-server\\app-route-turbo.runtime.dev.js:5:38696)\n' +
        '    at async AppRouteRouteModule.handle (C:\\Users\\rites\\Desktop\\pathway\\pathway\\node_modules\\.pnpm\\[email protected]_@opentelemetry+_f94025e8e3d705ec55e3c88f5d76a316\\node_modules\\next\\dist\\compiled\\next-server\\app-route-turbo.runtime.dev.js:5:45978)\n' +
        '    at async responseGenerator (C:\\Users\\rites\\Desktop\\pathway\\pathway\\apps\\web\\.next\\server\\chunks\\5d657_next_bc54bfd2._.js:10858:38)\n' +
        '    at async AppRouteRouteModule.handleResponse (C:\\Users\\rites\\Desktop\\pathway\\pathway\\node_modules\\.pnpm\\[email protected]_@opentelemetry+_f94025e8e3d705ec55e3c88f5d76a316\\node_modules\\next\\dist\\compiled\\next-server\\app-route-turbo.runtime.dev.js:1:187643)\n' +
        '    at async handleResponse (C:\\Users\\rites\\Desktop\\pathway\\pathway\\apps\\web\\.next\\server\\chunks\\5d657_next_bc54bfd2._.js:10920:32)\n' +
        '    at async handler (C:\\Users\\rites\\Desktop\\pathway\\pathway\\apps\\web\\.next\\server\\chunks\\5d657_next_bc54bfd2._.js:10972:13)\n' +
        '    at async DevServer.renderToResponseWithComponentsImpl (C:\\Users\\rites\\Desktop\\pathway\\pathway\\node_modules\\.pnpm\\[email protected]_@opentelemetry+_f94025e8e3d705ec55e3c88f5d76a316\\node_modules\\next\\dist\\server\\base-server.js:1422:9)\n' +
        '    at async DevServer.renderPageComponent (C:\\Users\\rites\\Desktop\\pathway\\pathway\\node_modules\\.pnpm\\[email protected]_@opentelemetry+_f94025e8e3d705ec55e3c88f5d76a316\\node_modules\\next\\dist\\server\\base-server.js:1474:24)\n' +
        '    at async DevServer.renderToResponseImpl (C:\\Users\\rites\\Desktop\\pathway\\pathway\\node_modules\\.pnpm\\[email protected]_@opentelemetry+_f94025e8e3d705ec55e3c88f5d76a316\\node_modules\\next\\dist\\server\\base-server.js:1514:32)\n' +
        '    at async DevServer.pipeImpl (C:\\Users\\rites\\Desktop\\pathway\\pathway\\node_modules\\.pnpm\\[email protected]_@opentelemetry+_f94025e8e3d705ec55e3c88f5d76a316\\node_modules\\next\\dist\\server\\base-server.js:1025:25)\n' +
        '    at async NextNodeServer.handleCatchallRenderRequest (C:\\Users\\rites\\Desktop\\pathway\\pathway\\node_modules\\.pnpm\\[email protected]_@opentelemetry+_f94025e8e3d705ec55e3c88f5d76a316\\node_modules\\next\\dist\\server\\next-server.js:393:17)\n' +
        '    at async DevServer.handleRequestImpl (C:\\Users\\rites\\Desktop\\pathway\\pathway\\node_modules\\.pnpm\\[email protected]_@opentelemetry+_f94025e8e3d705ec55e3c88f5d76a316\\node_modules\\next\\dist\\server\\base-server.js:916:17)\n' +
        '    at async C:\\Users\\rites\\Desktop\\pathway\\pathway\\node_modules\\.pnpm\\[email protected]_@opentelemetry+_f94025e8e3d705ec55e3c88f5d76a316\\node_modules\\next\\dist\\server\\dev\\next-dev-server.js:399:20\n' +
        '    at async Span.traceAsyncFn (C:\\Users\\rites\\Desktop\\pathway\\pathway\\node_modules\\.pnpm\\[email protected]_@opentelemetry+_f94025e8e3d705ec55e3c88f5d76a316\\node_modules\\next\\dist\\trace\\trace.js:157:20)\n' +
        '    at async DevServer.handleRequest (C:\\Users\\rites\\Desktop\\pathway\\pathway\\node_modules\\.pnpm\\[email protected]_@opentelemetry+_f94025e8e3d705ec55e3c88f5d76a316\\node_modules\\next\\dist\\server\\dev\\next-dev-server.js:395:24)\n' +
        '    at async invokeRender (C:\\Users\\rites\\Desktop\\pathway\\pathway\\node_modules\\.pnpm\\[email protected]_@opentelemetry+_f94025e8e3d705ec55e3c88f5d76a316\\node_modules\\next\\dist\\server\\lib\\router-server.js:240:21)\n' +
        '    at async handleRequest (C:\\Users\\rites\\Desktop\\pathway\\pathway\\node_modules\\.pnpm\\[email protected]_@opentelemetry+_f94025e8e3d705ec55e3c88f5d76a316\\node_modules\\next\\dist\\server\\lib\\router-server.js:437:24)\n' +
        '    at async requestHandlerImpl (C:\\Users\\rites\\Desktop\\pathway\\pathway\\node_modules\\.pnpm\\[email protected]_@opentelemetry+_f94025e8e3d705ec55e3c88f5d76a316\\node_modules\\next\\dist\\server\\lib\\router-server.js:485:13)\n' +
        '    at async Server.requestListener (C:\\Users\\rites\\Desktop\\pathway\\pathway\\node_modules\\.pnpm\\[email protected]_@opentelemetry+_f94025e8e3d705ec55e3c88f5d76a316\\node_modules\\next\\dist\\server\\lib\\start-server.js:226:13)',
      path: 'notifications.getUnreadCount'
    },
    meta: {
      response: Response {
        status: 401,
        statusText: 'Unauthorized',
        headers: Headers {
          vary: 'rsc, next-router-state-tree, next-router-prefetch, next-router-segment-prefetch, trpc-accept',
          'content-type': 'application/json',
          date: 'Tue, 18 Nov 2025 10:22:02 GMT',
          connection: 'keep-alive',
          'keep-alive': 'timeout=5',
          'transfer-encoding': 'chunked'
        },
        body: ReadableStream { locked: true, state: 'closed', supportsBYOB: true },
        bodyUsed: true,
        ok: false,
        redirected: false,
        type: 'basic',
        url: 'http://localhost:3000/api/trpc/notifications.getUnreadCount?batch=1&input=%7B%220%22%3A%7B%22json%22%3Anull%2C%22meta%22%3A%7B%22values%22%3A%5B%22undefined%22%5D%2C%22v%22%3A1%7D%7D%7D'
      },
      responseJSON: [Array]
    }
  },
  elapsedMs: 8367
}
 ⨯ [Error [TRPCClientError]: You must be logged in to access this resource] {
  cause: undefined,
  shape: [Object],
  data: [Object],
  meta: [Object],
  digest: '615110479'
}
 GET /agent 500 in 24303ms
 GET /api/trpc/notifications.getUnreadCount?batch=1&input=%7B%220%22%3A%7B%22json%22%3Anull%2C%22meta%22%3A%7B%22values%22%3A%5B%22undefined%22%5D%2C%22v%22%3A1%7D%7D%7D 200 in 585ms

as you can see at the last line the normal client side query runs and has 200 status code. Please help, I would be grateful. Ask any questions if you have.

I logged the headers when prefetching it logs

{
  accept: '*/*',
  'accept-encoding': 'gzip, deflate',
  'accept-language': '*',
  connection: 'keep-alive',
  host: 'localhost:3000',
  'sec-fetch-mode': 'cors',
  'user-agent': 'node',
  'x-forwarded-for': '::1',
  'x-forwarded-host': 'localhost:3000',
  'x-forwarded-port': '3000',
  'x-forwarded-proto': 'http'
}

no cookie sent.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.