tRPC, NextJS 14, experimental_createTRPCNextAppDirServer, How can I globally manage errors?
Unanswered
Basset Artésien Normand posted this in #help-forum
Basset Artésien NormandOP
For example if I get an Unauthorized error returned from tRPC and want to redirect the user back to / login.
And then at the moment I am calling a test endpoint in the API like this:
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 Normand For example if I get an Unauthorized error returned from tRPC and want to redirect the user back to / login.
ts
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:
ts
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>
)
}
I think its better to create a procedure which return the current session/user and check for the session/user in the page/middleware like this
export default async function Page() {
const session = await api.session.query()
if (!session) redirect('/login')
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>
)
}Basset Artésien NormandOP
Thanks @Ray
Regarding the use of
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
Regarding the use of
experimental_createTRPCNextAppDirServer i saw this post https://github.com/trpc/trpc/issues/4829Where 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_createTRPCNextAppDirServerBasset 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 insteadwhat I mean is use
createTRPCProxyClient instead of experimental_createTRPCNextAppDirServerBasset Artésien NormandOP
ill give it a shot thanks
@Ray I think its better to create a procedure which return the current session/user and check for the session/user in the page/middleware like this
ts
export default async function Page() {
const session = await api.session.query()
if (!session) redirect('/login')
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>
)
}
Basset Artésien NormandOP
is it possible to perform tRPC api request in middleware? it feels like I should be able to, I am just getting
headers() expects to have requestAsyncStorage, none available.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 expectedfetch('http://localhost:4000/trpc/userRouter.getCurrentUser', {
headers: Object.fromEntries(req.headers)
})Basset Artésien NormandOP
i was close. was looking into
Thanks again @Ray
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.
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
When trying to solve this with a customLink instead
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 errorBasset Artésien NormandOP
I've tested a
console.log within error(err) {} and it does get thereno
redirect doesn't work hereBasset Artésien NormandOP
damn
any alternative?
And why does it not work? So i can understand better