Integrate Clerk into your Next.js Pages Router app with tRPC
Learn how to integrate Clerk into your Next.js Pages Router application using tRPC(opens in a new tab). tRPC can be used with Clerk, but requires a few tweaks from a traditional Clerk + Next.js setup.
Prefer to get started quickly? Check out the following repo(opens in a new tab) which implements the essentials.
Installation
Install @clerk/nextjs
Clerk's Next.js SDK gives you access to prebuilt components and hooks, as well as our helpers for Next.js API routes, server-side rendering, and middleware.
terminalnpm install @clerk/nextjs
terminalyarn add @clerk/nextjs
terminalpnpm add @clerk/nextjs
Set environment keys
Below is an example of an .env.local
file. If you are signed into your Clerk Dashboard, your keys should become visible by clicking on the eye icon.
.env.localNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY={{pub_key}} CLERK_SECRET_KEY={{secret}}
Configure middleware
Middleware allows you to use Clerk server-side APIs. Create a middleware.ts
file. If your application uses the src/
directory, your middleware.ts
file should be placed inside the src/
folder. If you are not using a src/
directory, place the middleware.ts
file in your root directory.
middleware.tsimport { authMiddleware } from "@clerk/nextjs"; export default authMiddleware(); export const config = { matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'], };
This will protect every page by default, including API routes, and cause a redirect to Clerk's Account Portal. Read more about Clerk's middleware here.
Using Clerk in your tRPC Context
To use Clerk in your tRPC context, you will need to use our server helper getAuth
.
import * as trpc from '@trpc/server'; import * as trpcNext from '@trpc/server/adapters/next'; import { getAuth, SignedInAuthObject, SignedOutAuthObject } from '@clerk/nextjs/server'; interface AuthContext { auth: SignedInAuthObject | SignedOutAuthObject; } export const createContextInner = async ({ auth }: AuthContext ) => { return { auth, } } export const createContext = async ( opts: trpcNext.CreateNextContextOptions ) => { return await createContextInner({ auth: getAuth(opts.req) }) } export type Context = trpc.inferAsyncReturnType<typeof createContext>;
Accessing the context data in your application
Once you have context in your application, you can access the data in any procedure by using ctx
in your queries.
import { router, publicProcedure } from '../trpc'; export const exampleRouter = router({ hello: publicProcedure.query(({ctx}) => { return { greeting: `hello! ${ctx.auth?.userId}` } }) })
Using tRPC Middleware
In most cases, you need to protect your routes from users who aren't signed in. You can create a protected procedure.
import { initTRPC, TRPCError } from '@trpc/server' import superjson from 'superjson' import { type Context } from './context' const t = initTRPC.context<Context>().create({ transformer: superjson, errorFormatter({ shape }) { return shape } }) // check if the user is signed in, otherwise throw a UNAUTHORIZED CODE const isAuthed = t.middleware(({ next, ctx }) => { if (!ctx.auth.userId) { throw new TRPCError({ code: 'UNAUTHORIZED' }) } return next({ ctx: { auth: ctx.auth, }, }) }) export const router = t.router export const publicProcedure = t.procedure // export this procedure to be used anywhere in your application export const protectedProcedure = t.procedure.use(isAuthed)
Once you have created your procedure, you can use it in any router. The example below uses the protected procedure and returns the user's name.
import { router, protectedProcedure } from '../trpc' export const protectedRouter = router({ hello: protectedProcedure.query(({ ctx }) => { return { secret: `${ctx.auth?.userId} is using a protected procedure` } }) })