Configuring Clerk on the latest release of t3 with tRPC

PPavstermeister11/5/2023
Could anyone share a working example of configuring Clerk on the latest release of t3 with tRPC? I'm trying to follow Theo's t3 tutorial but Clerk and t3 have evolved enough since the recording to get me confused what would be the best practice. I am particularly interested in basic examples of trpc.ts and middleware.ts files.
CCsarChvz11/7/2023
Please! Integrate clerk with the new t3 app router! trpc.ts:/
NNico11/7/2023
I put out a demo doing this yesterday - https://x.com/nicoalbanese10/status/1721560882994377087?s=20
CCsarChvz11/8/2023
Big shout to nico! Ty man I did this repo https://github.com/CsarChvz/t3-appRouter-mantine-clerk-trpc
NNico11/8/2023
nice!
Vvibbin11/8/2023
And idea how to get Clerk auth to flow through into trpc context? I'm getting null values when trying to access my Clerk user details
import { initTRPC, TRPCError } from "@trpc/server";
import { type NextRequest } from "next/server";
import superjson from "superjson";
import { ZodError } from "zod";
import type {
SignedInAuthObject,
SignedOutAuthObject,
} from "@clerk/nextjs/server";
import { getAuth } from "@clerk/nextjs/server";

import { db } from "~/server/db";


interface CreateContextOptions {
req: NextRequest;
auth?: SignedInAuthObject | SignedOutAuthObject;
}

export const createInnerTRPCContext = (opts: CreateContextOptions) => {
const auth = getAuth(opts.req);
console.log(auth);

return {
...opts,
auth,
db,
};
};

export const createTRPCContext = (opts: { req: NextRequest }) => {
// Fetch stuff that depends on the request

return createInnerTRPCContext({
req: opts.req,
});
};
import { initTRPC, TRPCError } from "@trpc/server";
import { type NextRequest } from "next/server";
import superjson from "superjson";
import { ZodError } from "zod";
import type {
SignedInAuthObject,
SignedOutAuthObject,
} from "@clerk/nextjs/server";
import { getAuth } from "@clerk/nextjs/server";

import { db } from "~/server/db";


interface CreateContextOptions {
req: NextRequest;
auth?: SignedInAuthObject | SignedOutAuthObject;
}

export const createInnerTRPCContext = (opts: CreateContextOptions) => {
const auth = getAuth(opts.req);
console.log(auth);

return {
...opts,
auth,
db,
};
};

export const createTRPCContext = (opts: { req: NextRequest }) => {
// Fetch stuff that depends on the request

return createInnerTRPCContext({
req: opts.req,
});
};
CCsarChvz11/8/2023
@vibbin having the same issue! :/
Mmatiasmora11/8/2023
I need to do this too
BBootesVoid11/9/2023
Same
NNico11/9/2023
I can't get this to work with t3 after a few hours of trying. It works with a regular next-app with kirimase (kirimase's regular implementation of trpc is using nextcachelink vs batchstreamlink which may be the reason). I give up though. Would defer to @JacobMGEvans (Rustular CAO) who may be able to help. One thing I would mention @vibbin is that when using the app directory, clerk docs says to use auth() rather than getAuth().
CCsarChvz11/9/2023
@Nico yep! Also it gives me an error with trpc idk why
JJacobMGEvans11/9/2023
Yeah, there was an issue with the types, is that error you are referring to? If it is the type issue: https://github.com/clerk/javascript/pull/2049
CCsarChvz11/9/2023
Emmm, no Let me show u
Nnaabi11/10/2023
Did anyone solve it?
KKiKo11/10/2023
Also looking for a solution here I'm having issues whenever I try to make my root page public
BBootesVoid11/10/2023
Any clerk call from a trpc router rretuns null values. I tried useUser but doesn't work
NNico11/10/2023
@JacobMGEvans (Rustular CAO) no unfortunately clerk doesn't return a user in the auth() call. Clearing my cookies and cache and then logging in and console-logging the headers in the TRPC API route handler has some interesting findings that may lead to properly identifying the issue: 1) clerk cookies exist and seem like everything necessary for the auth function 2) clerk related headers conveying different message: [ 'x-clerk-auth-message', '' ], [ 'x-clerk-auth-reason', 'header-missing-non-browser' ], [ 'x-clerk-auth-status', 'signed-out' ], 'x-clerk-auth-message' => { name: 'x-clerk-auth-message', value: '' }, 'x-clerk-auth-reason' => { name: 'x-clerk-auth-reason', value: 'header-missing-non-browser' }, 'x-clerk-auth-status' => { name: 'x-clerk-auth-status', value: 'signed-out' }, Which is what i'm assuming is leading to the user object equalling null. I've been trying to find a solution but no luck yet. Any thoughts?
Nnaabi11/11/2023
based on what theo said on today's stream a solution would be replacing server.ts to:
import "server-only";
import { headers } from "next/headers";

import { appRouter } from "@/server/api/root";
import { auth } from "@clerk/nextjs";
import { db } from "@/server/db";

export const api = appRouter.createCaller({
auth: auth(),
headers: headers(),
db,
});
import "server-only";
import { headers } from "next/headers";

import { appRouter } from "@/server/api/root";
import { auth } from "@clerk/nextjs";
import { db } from "@/server/db";

export const api = appRouter.createCaller({
auth: auth(),
headers: headers(),
db,
});
CCsarChvz11/11/2023
Does it works?
SSpark11/11/2023
this seems to work for me as a temporary solution, but how would I continue to split edge and lambda, like in the OpenStatus repo? I previously had: server.ts
export const api = createTRPCProxyClient<AppRouter>({
transformer,
links: [
loggerLink({
enabled: (op) =>
process.env.NODE_ENV === "development" ||
(op.direction === "down" && op.result instanceof Error),
}),
endingLink({
headers: () => {
return {
cookie: cookies().toString(),
"x-trpc-source": "rsc",
};
},
}),
],
});
export const api = createTRPCProxyClient<AppRouter>({
transformer,
links: [
loggerLink({
enabled: (op) =>
process.env.NODE_ENV === "development" ||
(op.direction === "down" && op.result instanceof Error),
}),
endingLink({
headers: () => {
return {
cookie: cookies().toString(),
"x-trpc-source": "rsc",
};
},
}),
],
});
shared.ts
const lambdas = ["clerkRouter"];

export const endingLink = (opts?: {
headers?: HTTPHeaders | (() => HTTPHeaders);
}) =>
((runtime) => {
const sharedOpts = {
headers: opts?.headers,
} satisfies Partial<HTTPBatchLinkOptions>;

const edgeLink = unstable_httpBatchStreamLink({
...sharedOpts,
url: `${getBaseUrl()}/api/trpc/edge`,
})(runtime);
const lambdaLink = unstable_httpBatchStreamLink({
...sharedOpts,
url: `${getBaseUrl()}/api/trpc/lambda`,
})(runtime);

return (ctx) => {
const path = ctx.op.path.split(".") as [string, ...string[]];
const endpoint = lambdas.includes(path[0]) ? "lambda" : "edge";

const newCtx = {
...ctx,
op: { ...ctx.op, path: path.join(".") },
};
return endpoint === "edge" ? edgeLink(newCtx) : lambdaLink(newCtx);
};
}) satisfies TRPCLink<AppRouter>;
const lambdas = ["clerkRouter"];

export const endingLink = (opts?: {
headers?: HTTPHeaders | (() => HTTPHeaders);
}) =>
((runtime) => {
const sharedOpts = {
headers: opts?.headers,
} satisfies Partial<HTTPBatchLinkOptions>;

const edgeLink = unstable_httpBatchStreamLink({
...sharedOpts,
url: `${getBaseUrl()}/api/trpc/edge`,
})(runtime);
const lambdaLink = unstable_httpBatchStreamLink({
...sharedOpts,
url: `${getBaseUrl()}/api/trpc/lambda`,
})(runtime);

return (ctx) => {
const path = ctx.op.path.split(".") as [string, ...string[]];
const endpoint = lambdas.includes(path[0]) ? "lambda" : "edge";

const newCtx = {
...ctx,
op: { ...ctx.op, path: path.join(".") },
};
return endpoint === "edge" ? edgeLink(newCtx) : lambdaLink(newCtx);
};
}) satisfies TRPCLink<AppRouter>;
Nnaabi11/11/2023
I don't know how merging routers affects a caller but I understand that the previous method makes an API request, and you define the runtime in /api/trpc/edge/[trpc]/route.ts. By using a caller, it doesn't make an API request, so you are now responsible for setting the runtime in the appropriate page.tsx routes
Vvibbin11/11/2023
Would be interested in listening to the stream/discussion - what platform was that on?
Oofficiallywise11/13/2023
I'm glad to know it's not just me because I spent the entire weekend trying to get Clerk running on T3 using app router and couldn't figure it out.
Mmatiasmora11/14/2023
Still can't figure this out 😦
JJEM11/14/2023
https://github.com/t3-oss/create-t3-app/issues/1656 hey I was able to figure out the issue it was with a recent change to the headers in the trpc provider but it seems maybe theo mentioned a better fix on stream?
Eeula111/15/2023
any updates on clerk + trpc + app router? afaik there is still no complete implementation with no issues were you typing? @Spark
SSpark11/15/2023
I haven’t seen a full implementation yet. I have used createCaller with some success in server.ts, but I am running into issues with useMutation on the client atm.
Ppineappaul11/15/2023
I have Clerk + T3 fully integrated, except for a nasty bug we are stuck on. With this bug, authenticated requests to the TRPC server can only be made after logging in and refreshing the page. (see https://discord.com/channels/966627436387266600/1174091052102197398/1174091052102197398) No idea why!
Eeula111/15/2023
any word from clerk devs yet? this is kinda insane trpc + app router has been out for so long
Sshikishikichangchang11/16/2023
Im discussing this with Roy at Clerk https://discord.com/channels/856971667393609759/1172416025271226409 the one possible "solution" is this, in createTRPCContext:
const sessionToken = opts.req.cookies.get("__session")?.value ?? "";
const decodedJwt = decodeJwt(sessionToken);
const session = await clerkClient.sessions.verifySession(
decodedJwt.payload.sid,
sessionToken,
);
const sessionToken = opts.req.cookies.get("__session")?.value ?? "";
const decodedJwt = decodeJwt(sessionToken);
const session = await clerkClient.sessions.verifySession(
decodedJwt.payload.sid,
sessionToken,
);
Caveat: subject to rate limit
Rroyanger11/16/2023
I was just made aware of this thead. The suggestion Theo made on stream seems like the correct path here. I suspect that once that's implemented the req.header might include the cookie information that clerk is expecting. What's the issue with Edge/Lambda @Spark? Does anyone happen to have a relatively basic repo trying Theo's suggestion they can share with me to look into this? And unfortunately most of the conversation with shikishikichangchang on the Clerk discord was more general stuff or talk about App Router. Its not really relevant here. You can solve "2) clerk related headers conveying different message:" by making the trpc route public. And since usually auth checks are done trpc side this isn't an issue. Add something like publicRoutes: ["/api/<your trpc route>"] or publicRoutes: ["/api(.*)"] to your middleware.ts. The latter makes all api routes public. However after that step the cookie from Clerk still isn't normally in the header. What Theo mentioned might solve that -- need to look into that.
Nnaabi11/16/2023
Using a caller fixes all my problems, but I had to tweak server.ts to make it build without errors, but still havent solved the issue using the proxyclient
import "server-only";
import { headers } from "next/headers";

import { appRouter } from "@/server/api/root";
import { auth } from "@clerk/nextjs";
import { db } from "@/server/db";

export function caller() {
// cant use auth() outside a function, build fails
return appRouter.createCaller({
auth: auth(),
headers: headers(),
db,
});
}
import "server-only";
import { headers } from "next/headers";

import { appRouter } from "@/server/api/root";
import { auth } from "@clerk/nextjs";
import { db } from "@/server/db";

export function caller() {
// cant use auth() outside a function, build fails
return appRouter.createCaller({
auth: auth(),
headers: headers(),
db,
});
}
SSpark11/16/2023
nothing Clerk specific, just that using createCaller forces you to set the runtime in page.tsx instead of the way I was splitting edge/lambda before
Eeula111/17/2023
@JacobMGEvans (Rustular CAO) can we get some more support on this issue? clerk has been silent + 100s people struggling
JJacobMGEvans11/17/2023
What do you mean Clerk has been silent?
Eeula111/17/2023
@JacobMGEvans (Rustular CAO) it's quite clear – there are 6/7 open issues on discord, mutliple ad-hoc discussions in the general channels, and 2 issues on github – and there has been very minimal interaction between clerk staff and the community if at all
JJacobMGEvans11/17/2023
On the Clerk Discord?
Eeula111/17/2023
i think between clerk, theo, and trpc discords there are 12ish open issues yes and almost 0 support from clerk officially
JJacobMGEvans11/17/2023
We officially give support in the Clerk discord 🙂
Eeula111/17/2023
really? then why are there 4+ active issues on this and zero support from clerk in your server?
No description
Eeula111/17/2023
these are just the ones i follow
JJacobMGEvans11/17/2023
Ping me & Roy in those
Eeula111/17/2023
definitely! also - i don't mean to be rude - just being transparent and voicing the signals from the community that the support on this has been quite far below what you'd usually expect for a p0 (!!!) issue for paying customers
Ppineappaul11/17/2023
After speaking with Roy last night, I will be releasing a video for the team @JacobMGEvans (Rustular CAO) demonstrating one symptom, as he said this will be useful. I can tag you when this goes up as well. Eating some breakfast before I get going Whatever we can do to help the team troubleshoot the issue faster, here to help with as long as we have bandwidth !
JJacobMGEvans11/17/2023
You're fine, it's frustrating. We had a week we're it was me and another DevRel doing tickets and we couldn't keep up. That's on us
Eeula111/17/2023
thank you for understanding!
Ppineappaul11/17/2023
Discord is arguably one of the most confusing comms platforms too, my last start up we were Discord centric and it was a living hell, so we totally understand the thread spread
JJacobMGEvans11/17/2023
Me and Roy are working on improving that, he wrote up some support features for a bot and I'm adding some stuff too
NNotLuksus11/17/2023
There is still no clear solution for this right? Like a simple example repo?
Ppineappaul11/17/2023
@NotLuksus I have Clerk working with tRPC in this example repo, however there is still one bug present in my implementation where the auth object on the tRPC router server-side is only updated upon a full-page reload. You can check the example repo here: https://github.com/paulmikulskis/popup
If you have a suggestion on how to complete the fix, please open a branch so all can benefit 🙏
The solution is likely due to needing to call auth() from the client-side state, but I am not 100% clear on how to do this just yet. Roy provided a reasonable approach that I want to dig into a bit more today for inserting the client-side Clerk auth state into the tRPC context. (see https://discord.com/channels/966627436387266600/1170848397570359376/1174696019720671304) The issue with createCaller is that this solution is somewhat of an anti-pattern, since we want to keep the tRPC Context as the "caller" @JacobMGEvans (Rustular CAO) @Roy Anger Video walkthrough as promised, with two replay recordings for demonstrating: - what happens when things don't work (Replay recording 1 👉 https://app.replay.io/recording/builderclerk-broken--beda20eb-378a-4000-9aa4-5b558d905a82) - what happens when things do work (Replay recording 2 👉 https://app.replay.io/recording/builderclerk-working-demo--dae89c78-6cef-45f9-862b-d9ce26abcde1) Video walkthrough (3-minutes): https://youtu.be/ct47SQF0NMo Would be great to know when you folks are all set with the Replay links so I can delete them.
JJacobMGEvans11/17/2023
Did you add this to Clerk support channel too @pineappaul
Ppineappaul11/17/2023
I have not. I Will add shortly
Rroyanger11/17/2023
Yes, please add this to your iframe thread so I can help you.
NNotLuksus11/18/2023
While trying to figure this out I noticed that this is an issue that, at least for me, only occurs in dev. In my production deployment I have full access to the clerk user / auth objects in trpc
LLeoMachado11/20/2023
Anyone got any lucky on getting this sorted out? Using the createCaller is indeed the best solution?
Ppineappaul11/20/2023
@LeoMachado how do you plan on integrating the createCaller paradigm into your NextJS app? I might be totally missing the mark here, but it feels that this is an anti-pattern, so I am curious on your take there
Rroyanger11/22/2023
T3 has or is about to release a fix for this.
SSpark11/23/2023
anyone have a clerk example working with the newest create-t3-app? where do we set auth?
Eeula111/23/2023
i do https://github.com/t3-oss/create-t3-app/pull/1670 this works depends tho– this is only required if you're using RSC ^^ if you don't want to use RSC for some reason, then the solution is even easier search the help threads for the cookies solution - its really easy
SSpark11/23/2023
right, I implemented those changes, but are you still setting auth in trpc.ts on the ctx?
Eeula111/23/2023
import { initTRPC, TRPCError } from "@trpc/server";
import { type NextRequest } from "next/server";
import superjson from "superjson";
import { ZodError } from "zod";

import { db } from "~/server/db";

import { clerkClient, decodeJwt, getAuth, type User, type SignedInAuthObject, SignedOutAuthObject } from '@clerk/nextjs/server';
import { auth } from "@clerk/nextjs";


interface CreateContextOptions {
headers: Headers;
db: typeof db;
// auth: User;
auth: SignedInAuthObject | SignedOutAuthObject;
}

export const createTRPCContext = async (opts: { headers: Headers }) => {
const session = auth();
// const session = getAuth(opts)

return {
db,
auth: session,
...opts,
};
};


const t = initTRPC.context<typeof createTRPCContext>().create({
transformer: superjson,
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.cause instanceof ZodError ? error.cause.flatten() : null,
},
};
},
});

/**
* 3. ROUTER & PROCEDURE (THE IMPORTANT BIT)
*
* These are the pieces you use to build your tRPC API. You should import these a lot in the
* "/src/server/api/routers" directory.
*/

/**
* This is how you create new routers and sub-routers in your tRPC API.
*
* @see https://trpc.io/docs/router
*/
export const createTRPCRouter = t.router;

const isAuthed = t.middleware(({ next, ctx }) => {
if (!ctx.auth.userId) {
throw new TRPCError({ code: "UNAUTHORIZED", message: "Not authenticated" });
}
return next({
ctx: {
auth: ctx.auth,
db,
},
});
});


export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(isAuthed);
import { initTRPC, TRPCError } from "@trpc/server";
import { type NextRequest } from "next/server";
import superjson from "superjson";
import { ZodError } from "zod";

import { db } from "~/server/db";

import { clerkClient, decodeJwt, getAuth, type User, type SignedInAuthObject, SignedOutAuthObject } from '@clerk/nextjs/server';
import { auth } from "@clerk/nextjs";


interface CreateContextOptions {
headers: Headers;
db: typeof db;
// auth: User;
auth: SignedInAuthObject | SignedOutAuthObject;
}

export const createTRPCContext = async (opts: { headers: Headers }) => {
const session = auth();
// const session = getAuth(opts)

return {
db,
auth: session,
...opts,
};
};


const t = initTRPC.context<typeof createTRPCContext>().create({
transformer: superjson,
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.cause instanceof ZodError ? error.cause.flatten() : null,
},
};
},
});

/**
* 3. ROUTER & PROCEDURE (THE IMPORTANT BIT)
*
* These are the pieces you use to build your tRPC API. You should import these a lot in the
* "/src/server/api/routers" directory.
*/

/**
* This is how you create new routers and sub-routers in your tRPC API.
*
* @see https://trpc.io/docs/router
*/
export const createTRPCRouter = t.router;

const isAuthed = t.middleware(({ next, ctx }) => {
if (!ctx.auth.userId) {
throw new TRPCError({ code: "UNAUTHORIZED", message: "Not authenticated" });
}
return next({
ctx: {
auth: ctx.auth,
db,
},
});
});


export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(isAuthed);
@Spark yes
SSpark11/23/2023
awesome, thanks! didn't realize we could just use the auth() function instead of getAuth() now. have you been able to useMutation? I keep getting an error, but it might just be my mistake.
Eeula111/23/2023
what's the error?
SSpark11/23/2023
TypeError: queryClient.getMutationDefaults is not a function
TypeError: queryClient.getMutationDefaults is not a function

Looking for more? Join the community!

Want results from more Discord servers?
Add your server
Recommended Posts
Nextauth jwt configurationI am making a simple task manager application using google apis with the t3 stack and I am having diHow do I fix this query, I was following a tutorial that doesn't use the t3 stackI'm trying to replicate this query (shown below) into t3 stack but the "onSuccess" property doesn't Button creates vertical gap in grid, button doesn'tThis is possibly a really really dumb question, but why does the button result in a vertical gap in Is anyone using the nextjs app router for their frontend and pages for trpc?If you could share an example repo I'd love to see how you've implemented thisSession exists in server component, not in trpc context.Inside of a server component I call: ` const session = await getServerAuthSession(); console.loQ: About System Design😦Pusher auth server on different hostThis question is related to `pusher-js`. I have a channel authorization server at a different physicIs there a way to load my .env from t3-env in one off scripts?I believe that is what this issue is asking for? https://github.com/t3-oss/t3-env/issues/97upstream image response failed: 403can't access the image on uploadthing ```ts <AspectRatio ratio={16 / 9}> <Image aHow to get rid of this unnecessary code while preventing eslint from complaining.`const index = items.findIndex((i) => i.productID == productID); if (index != -1) { const existintrpc mutations for fetching dataRecently was told that using mutations to fetch list data is bad. I can agree with this on a high leMaking all my components 'use client' while maintaining protectedProceduresHi. I'm using the full T3 stack with prisma, next auth, and nextjs with the app router. I want to siServer Component Data RevalidationHi all, searched and did not find a discussion on this yet but new here so apologies if I overlookedPrettier hangs when using with tailwind pluginFirst I've noticed it in VSCode, than tried via CLI - the same error. Without plugin everything worknext dont build in github ciHey guys I started building an app with the t3 stack a few days ago, and now I wanted to write a Gitcreate t3 app @ latest mutation result not mappableIve made sample t3 app with the latest build just to create a trpc router that helps me reach an extGraphQl and NextJS 13+ for multiple, concurrent/parallel DB queries?I know this could be a problem with architecture/design, but how would you most efficiently query (rLinks within one another (nextjs)Hey, having a problem with having link buttons within each other. When I try to give the bottom buttServer component rendered by client component as children prop throwing errori'm trying to fetch some data from a server component (`StatusImages`) that is being rendered as theEnhancing Performance with Pre-fetching Paginated Queries in tRPCHello, I'm currently focused on boosting the performance of a website that incorporates tRPC. Our s