TTC
Theo's Typesafe Cult
questions
Configuring Clerk on the latest release of t3 with tRPC
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.Please! Integrate clerk with the new t3 app router!
trpc.ts:/
I put out a demo doing this yesterday - https://x.com/nicoalbanese10/status/1721560882994377087?s=20
Big shout to nico!
Ty man
I did this repo
https://github.com/CsarChvz/t3-appRouter-mantine-clerk-trpc
nice!
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,
});
};
@vibbin having the same issue! :/
I need to do this too
Same
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().
@Nico yep!
Also it gives me an error with trpc idk why
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
Emmm, no
Let me show u
Did anyone solve it?
Also looking for a solution here
I'm having issues whenever I try to make my root page public
Any clerk call from a trpc router rretuns null values.
I tried useUser but doesn't work
@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?
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,
});
Does it works?
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
shared.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",
};
},
}),
],
});
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>;
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
routesWould be interested in listening to the stream/discussion - what platform was that on?
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.
Still can't figure this out 😦
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?
any updates on clerk + trpc + app router?
afaik there is still no complete implementation with no issues
were you typing?
@Spark
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.
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!
any word from clerk devs yet?
this is kinda insane
trpc + app router has been out for so long
Im discussing this with Roy at Clerk https://discord.com/channels/856971667393609759/1172416025271226409
the one possible "solution" is this, in createTRPCContext:
Caveat: subject to rate limit
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,
);
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.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,
});
}
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
@JacobMGEvans (Rustular CAO) can we get some more support on this issue? clerk has been silent + 100s people struggling
What do you mean Clerk has been silent?
@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
On the Clerk Discord?
i think between clerk, theo, and trpc discords there are 12ish open issues yes
and almost 0 support from clerk officially
We officially give support in the Clerk discord 🙂
really? then why are there 4+ active issues on this and zero support from clerk in your server?

these are just the ones i follow
Ping me & Roy in those
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
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 !
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
thank you for understanding!
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
Me and Roy are working on improving that, he wrote up some support features for a bot and I'm adding some stuff too
There is still no clear solution for this right?
Like a simple example repo?
@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.Did you add this to Clerk support channel too @pineappaul
I have not. I Will add shortly
Yes, please add this to your iframe thread so I can help you.
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
Anyone got any lucky on getting this sorted out? Using the createCaller is indeed the best solution?
@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 thereT3 has or is about to release a fix for this.
anyone have a clerk example working with the newest create-t3-app? where do we set auth?
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
right, I implemented those changes, but are you still setting auth in trpc.ts on the ctx?
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);
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.
what's the error?
TypeError: queryClient.getMutationDefaults is not a function
TypeError: queryClient.getMutationDefaults is not a function
Looking for more? Join the community!
TTC
Theo's Typesafe Cult
questions
TTC
Theo's Typesafe Cult
questions
Want results from more Discord servers?
Recommended PostsNextauth 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