Next.js Discord

Discord Forum

tRPC, NextJS 14, experimental_createTRPCNextAppDirServer, How can I globally manage errors?

Unanswered
Basset Artésien Normand posted this in #help-forum
Open in Discord
Basset Artésien NormandOP
For example if I get an Unauthorized error returned from tRPC and want to redirect the user back to / login.

export const api = experimental_createTRPCNextAppDirServer<AppRouter>({
  config() {
    return {
      transformer: superjson,
      links: [
        loggerLink({
          enabled: (opts) => {
            //console.log(opts)
            return (
              process.env.NODE_ENV === 'development' ||
              (opts.direction === 'down' && opts.result instanceof Error)
            )
          },
        }),
        httpBatchLink({
          url: 'http://localhost:4000/trpc',
          headers: Object.fromEntries(headers()),
          fetch(url, options) {
            return fetch(url, {
              ...options,
              credentials: 'include',
            })
          },
        }),
      ],
    }
  },
})


And then at the moment I am calling a test endpoint in the API like this:

export default async function Page() {
  return (
    <main>
      <h1 className="text-xl md:text-2xl">Dashboard</h1>
      <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
        <p>Dashboard Page.tsx</p>
        // Here
        <p>{await api.healthcheck.query()}</p>
      </div>
    </main>
  )
}

25 Replies

Basset Artésien NormandOP
Thanks @Ray

Regarding the use of experimental_createTRPCNextAppDirServer i saw this post https://github.com/trpc/trpc/issues/4829
Where Julius said he won't continue working on it for now. Is this the only option for me, feels a little unsafe to build an app on it when it could change or not be maintained
check out create-t3-app which doesn't use experimental_createTRPCNextAppDirServer
Basset Artésien NormandOP
yeah i have but they don't deploy the server separately
export const api = createTRPCProxyClient<AppRouter>({
  transformer,
  links: [
    loggerLink({
      enabled: (op) =>
        process.env.NODE_ENV === "development" ||
        (op.direction === "down" && op.result instanceof Error),
    }),
    /**
     * Custom RSC link that lets us invoke procedures without using http requests. Since Server
     * Components always run on the server, we can just call the procedure as a function.
     */
    () =>
      ({ op }) =>
        observable((observer) => {
          createContext()
            .then((ctx) => {
              return callProcedure({
                procedures: appRouter._def.procedures,
                path: op.path,
                rawInput: op.input,
                ctx,
                type: op.type,
              });
            })
            .then((data) => {
              observer.next({ result: { data } });
              observer.complete();
            })
            .catch((cause: TRPCErrorResponse) => {
              observer.error(TRPCClientError.from(cause));
            });
        }),
  ],
});

They use this with the custom link. Do you know if I could just change the custom link for httpBatchLink?
well I meant check out how they setup
you could use httpBatchLink instead
what I mean is use createTRPCProxyClient instead of experimental_createTRPCNextAppDirServer
Basset Artésien NormandOP
ill give it a shot thanks
Basset Artésien NormandOP
import { api } from './app/trpc/server'
import { TRPCClientError } from '@trpc/client'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export async function middleware(request: NextRequest) {
  console.log('123456')
  const user = await api.userRouter.getCurrentUser.query()
  if (user) return NextResponse.next()

  return NextResponse.redirect('/login')
}

export const config = {
  matcher: '/dashboard/',
}


I am just trying to do something like the above.
But as soon as I can the getCurrentUser i get the headers error: headers() expects to have requestAsyncStorage, none available.
and the console.log does not run.
Without the getCurrentUser call the console logs as expected
Basset Artésien NormandOP
i was close. was looking into fetchRequestHandler
Thanks again @Ray
Basset Artésien NormandOP
Is there a reason it doesn't work as I did it? Just so i understand why?
Basset Artésien NormandOP
I must admit, It feels like making an extra API call for every request feels inefficient.
The best solution would be that a user is redirected when trying to make a normal procedure request and receiving an 'UNAUTHORIZED' back from tRPC.
Would the only way be to create my own link?
It does feel slightly odd that there is no way to handle this natively.
Basset Artésien NormandOP
Is there a reason redirectwould not work here:
When trying to solve this with a customLink instead

import { redirect } from 'next/navigation'

export const customLink: TRPCLink<AppRouter> = () => {
  return ({ next, op }) => {
    return observable((observer) => {
      const unsubscribe = next(op).subscribe({
        next(value) {
          observer.next(value)
        },
        error(err) {
          observer.error(err)
          if (err?.data?.code === 'UNAUTHORIZED') {
            redirect('/login')
          }
        },
        complete() {
          observer.complete()
        },
      })
      return unsubscribe
    })
  }
}
Basset Artésien NormandOP
@Ray Could you offer any help with this?
@Basset Artésien Normand <@743561772069421169> Could you offer any help with this?
redirect only work if next is able to catch the error
Basset Artésien NormandOP
I've tested a console.log within error(err) {} and it does get there
no redirect doesn't work here
Basset Artésien NormandOP
damn
any alternative?
And why does it not work? So i can understand better