Issue with trpc fetch when trying to pass Clerk auth to context

EEntropy5/25/2023
I'm currently trying to add Clerk auth to my trpc context, but I just keep getting this error:
Error: Unexpected token '<', "<!DOCTYPE "... is not valid JSON

This is my code:

context.ts
import { NextRequest } from 'next/server'
import { getAuth } from '@clerk/nextjs/server'
import { FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch'

export function createContext(opts: FetchCreateContextFnOptions) {
  const auth = getAuth(opts.req as NextRequest)

  return {
    auth,
    headers: opts && Object.fromEntries(opts.req.headers)
  }
}

export type Context = Awaited<ReturnType<typeof createContext>>


trpc.ts
import { TRPCError, initTRPC } from '@trpc/server'
import superjson from 'superjson'
import { ZodError } from 'zod'

import { Context } from './context'

const t = initTRPC.context<Context>().create({
  transformer: superjson,
  errorFormatter(opts) {
    const { shape, error } = opts
    return {
      ...shape,
      data: {
        ...shape.data,
        zodError:
          error.code === 'BAD_REQUEST' && error.cause instanceof ZodError
            ? error.cause.flatten()
            : null
      }
    }
  }
})

const isAuthed = t.middleware(({ next, ctx }) => {
  console.log('isAuthed', ctx.auth)

  if (!ctx.auth.userId) {
    throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Not authenticated' })
  }
  return next({
    ctx: {
      auth: ctx.auth
    }
  })
})

export const router = t.router
export const publicProcedure = t.procedure
export const protectedProcedure = t.procedure.use(isAuthed)
EEntropy5/25/2023
Everything also seems to work fine when I log in and make a call to my protectedProcedure, it's only a problem when I log out.
Ssacul5/25/2023
Kinda seems like you're getting the default 404 html page from next as a response. So maybe my first guess would be that you set up the route handler incorrectly
Ssacul5/25/2023
At least everytime I encountered an error like that it was because I had set up something wrong in the route handler
Ssacul5/25/2023
did you export the methods correctly in the route handler like this?
export {
  handler as GET,
  handler as POST,
}
EEntropy5/25/2023
Yep I did here's my route handler


api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'

import { createContext } from '~/server/api/context'
import { appRouter } from '~/server/api/root'

const handler = (req: Request) =>
  fetchRequestHandler({
    endpoint: '/api/trpc',
    req,
    router: appRouter,
    createContext
  })

export { handler as GET, handler as POST }
EEntropy5/26/2023
So after a lot of messing around it seems to caused by something with the Clerk Middleware. I'm still not exactly sure what is the problem or why though.
EEntropy5/26/2023
here's a link to a repo with the same issue as well:

https://github.com/Entropy-10/clerk-trpc-issue
Kkoko13375/26/2023
I implemented this the other week, this is how I added the clerk auth to the context

import { getAuth } from '@clerk/nextjs/server'
import { inferAsyncReturnType } from '@trpc/server'
import { CreateNextContextOptions } from '@trpc/server/adapters/next'

import { prisma } from '~/server/prisma'

/** Context creator for testing purposes */
const createInnerTRPCContext = ({ req }: CreateNextContextOptions) => {
  return {
    prisma,
    auth: getAuth(req),
  }
}

/**
 * This is the actual context you will use in your router. It will be used to process every request
 * that goes through your tRPC endpoint.
 *
 * @see https://trpc.io/docs/context
 */
export const createTRPCContext = (opts: CreateNextContextOptions) => {
  return createInnerTRPCContext(opts)
}

export type TRPCContext = inferAsyncReturnType<typeof createTRPCContext>
Kkoko13375/26/2023
and for the middleware:

import { getAuth, withClerkMiddleware } from '@clerk/nextjs/server'

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

// Set the paths that don't require the user to be signed in
const publicPaths = ['/sign-in*', '/sign-up*', '/landing'],
  isPublic = (path: string) =>
    publicPaths.find((x) => path.match(new RegExp(`^${x}$`.replace('*$', '($|/)')))),
  authedEndpoints = ['/api/trpc*'],
  isAuthedEndpoint = (path: string) =>
    authedEndpoints.find((x) => path.match(new RegExp(`^${x}$`.replace('*$', '($|/)'))))

export default withClerkMiddleware((request: NextRequest) => {
  if (isPublic(request.nextUrl.pathname)) {
    return NextResponse.next()
  }
  // if the user is not signed in redirect them to the sign in page.
  const { userId } = getAuth(request)

  // prevent trpc calls if user not authed
  if (isAuthedEndpoint(request.nextUrl.pathname) && !userId) {
    return NextResponse.json(
      {
        message: 'UNAUTHORIZED',
      },
      { status: 400 }
    )
  }

  if (!userId) {
    // redirect the users to sign in
    const signInUrl = new URL('/sign-in', request.url)
    signInUrl.searchParams.set('redirect_url', request.url)
    return NextResponse.redirect(signInUrl)
  }

  return NextResponse.next()
})

// Stop Middleware running on static files and public folder
export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - _next
     * - static (static files)
     * - favicon.ico (favicon file)
     * - public folder
     * - trpc and api endpoints
     */
    '/((?!static|.*\\..*|_next|favicon.ico).*)',
    '/',
    '/(api|trpc)(.*)',
  ],
}
Kkoko13375/26/2023
in my case any TRPC calls would happen post auth, so I added the auth check in the middleware, but feel free to remove that if your case differs
EEntropy5/26/2023
I see thank you for the information! It turns out that Clerk will send everything to the sign in page including api's so I just had to add my api route for trpc to my clerk middleware public routes.
Kkoko13375/28/2023
cool, glad I was of help 🙂