Chirp Tutorial: tRPC failed on <no-path>:

Following along with the theo turtorial https://youtu.be/YkOSUVzOAA4?t=5612 I have my trpc.ts file nearly identical but I run into this error:
tRPC failed on <no-path>: You need to use "withClerkMiddleware" in your Next.js middleware file. You also need to make sure that your middleware matcher is configured correctly and matches this route or page. See https://clerk.com/docs/quickstarts/get-started-with-nextjs It's pretty much the exact same file yet I get this error. Just makes no sense. Reproduction Repo: https://github.com/Apestein/chirp
import { type CreateNextContextOptions } from "@trpc/server/adapters/next"
import { prisma } from "~/server/db"

export const createTRPCContext = (opts: CreateNextContextOptions) => {
const { req } = opts
const userId = getAuth(req).userId
return {
prisma,
userId,
}
}

import { TRPCError, initTRPC } from "@trpc/server"
import superjson from "superjson"
import { ZodError } from "zod"
import { getAuth } from "@clerk/nextjs/server"

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,
},
}
},
})

export const createTRPCRouter = t.router

export const publicProcedure = t.procedure

const enforceUserIsAuthed = t.middleware(async ({ ctx, next }) => {
if (!ctx.userId)
throw new TRPCError({
code: "UNAUTHORIZED",
})

return next({
ctx: {
userId: ctx.userId,
},
})
})

export const privateProcedure = t.procedure.use(enforceUserIsAuthed)
import { type CreateNextContextOptions } from "@trpc/server/adapters/next"
import { prisma } from "~/server/db"

export const createTRPCContext = (opts: CreateNextContextOptions) => {
const { req } = opts
const userId = getAuth(req).userId
return {
prisma,
userId,
}
}

import { TRPCError, initTRPC } from "@trpc/server"
import superjson from "superjson"
import { ZodError } from "zod"
import { getAuth } from "@clerk/nextjs/server"

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,
},
}
},
})

export const createTRPCRouter = t.router

export const publicProcedure = t.procedure

const enforceUserIsAuthed = t.middleware(async ({ ctx, next }) => {
if (!ctx.userId)
throw new TRPCError({
code: "UNAUTHORIZED",
})

return next({
ctx: {
userId: ctx.userId,
},
})
})

export const privateProcedure = t.procedure.use(enforceUserIsAuthed)
25 Replies
James Perkins
James Perkins14mo ago
try this as your middleware matcher
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - _next
* - static (static files)
* - favicon.ico (favicon file)
*/
"/(.*?trpc.*?|(?!static|.*\\..*|_next|favicon.ico).*)",
"/"
],
};
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - _next
* - static (static files)
* - favicon.ico (favicon file)
*/
"/(.*?trpc.*?|(?!static|.*\\..*|_next|favicon.ico).*)",
"/"
],
};
Apestein
Apestein14mo ago
yeah that worked but can you explain to me why?
James Perkins
James Perkins14mo ago
trpc routes on the api layer use a period to denote the procedure. So it needs to be added to the matcher for it to run properly.
bozobit
bozobit14mo ago
Hi @James Perkins , I had the exact same issue, but when I updated the middleware matcher as you suggested, it just kept calling the middleware in a loop and would never execute the public (prisma) procedure. Any thoughts?
James Perkins
James Perkins14mo ago
check your clock( system clock) if it’s skewed outside the clock skew you are going to be pushed in a redirect loop as we try to issue you a new jwt and it’s constantly expired
bozobit
bozobit14mo ago
Sorry, but I'm not sure what you mean. I can try to figure that it out, but can you direct me?
James Perkins
James Perkins14mo ago
On your computer is the time correct
bozobit
bozobit14mo ago
Yes.
James Perkins
James Perkins14mo ago
so when you say it redirects. do you manage to login or does the page just constantly refresh?
bozobit
bozobit14mo ago
I put a console.log statement in the withClerkMiddleware function inside middleware.ts, and when I cal the public procedure on a public path, I get this loop, but the TRPC (prisma) query never executes. CLERK MIDDLEWARE RUNNING RETURNING A PUBLIC PATH CLERK MIDDLEWARE RUNNING RETURNING A PUBLIC PATH CLERK MIDDLEWARE RUNNING CLERK MIDDLEWARE RUNNING RETURNING A PUBLIC PATH CLERK MIDDLEWARE RUNNING CLERK MIDDLEWARE RUNNING RETURNING A PUBLIC PATH CLERK MIDDLEWARE RUNNING CLERK MIDDLEWARE RUNNING RETURNING A PUBLIC PATH CLERK MIDDLEWARE RUNNING CLERK MIDDLEWARE RUNNING export default withClerkMiddleware((request: NextRequest) => { console.log("CLERK MIDDLEWARE RUNNING") if (isPublic(request.nextUrl.pathname)) { console.log("RETURNING A PUBLIC PATH") return NextResponse.next(); } // if the user is not signed in redirect them to the sign in page. const { userId } = getAuth(request); if (!userId) { // redirect the users to /pages/sign-in/[[...index]].ts const signInUrl = new URL("/sign-in", request.url); signInUrl.searchParams.set("redirect_url", request.url); return NextResponse.redirect(signInUrl); } return NextResponse.next(); });
James Perkins
James Perkins14mo ago
one second Okay can you do this:
export default withClerkMiddleware((request: NextRequest) => {
const { userId,debug } = getAuth(request);
console.log(debug())
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);

if (!userId) {
// redirect the users to /pages/sign-in/[[...index]].ts

const signInUrl = new URL("/sign-in", request.url);
signInUrl.searchParams.set("redirect_url", request.url);
return NextResponse.redirect(signInUrl);
}
return NextResponse.next();
});
export default withClerkMiddleware((request: NextRequest) => {
const { userId,debug } = getAuth(request);
console.log(debug())
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);

if (!userId) {
// redirect the users to /pages/sign-in/[[...index]].ts

const signInUrl = new URL("/sign-in", request.url);
signInUrl.searchParams.set("redirect_url", request.url);
return NextResponse.redirect(signInUrl);
}
return NextResponse.next();
});
bozobit
bozobit14mo ago
Will give it a go. FYI. I have a previous version of this that works, but that is before Theo made the changes to the trpc.ts file in his video. I'm just trying to figure out what is breaking it. I had to comment out the second reference to userId and got it to work. this is returned (multiple times) { apiKey: '', secretKey: 'sk_test', apiUrl: 'https://api.clerk.dev', apiVersion: 'v1', authStatus: 'signed-out', authMessage: undefined, authReason: 'standard-signed-out', jwtKey: '' }
James Perkins
James Perkins14mo ago
Okay what do your public paths look like? Are you using windows?
bozobit
bozobit14mo ago
const publicPaths = ["/", "/sign-in", "/sign-up", "/_SANDBOX/PageB*"]; Yes, windows
James Perkins
James Perkins14mo ago
Okay so lets do this to make it easier for me to debug.
export default withClerkMiddleware((request: NextRequest) => {
const { userId ,debug } = getAuth(request);
console.log(debug())
if (isPublic(request.nextUrl.pathname)) {
console.log(pathname)
return NextResponse.next();
}
if (!userId) {
// redirect the users to /pages/sign-in/[[...index]].ts
console.log(request.url)
const signInUrl = new URL("/sign-in", request.url);
signInUrl.searchParams.set("redirect_url", request.url);
return NextResponse.redirect(signInUrl);
}
return NextResponse.next();
});

export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - _next
* - static (static files)
* - favicon.ico (favicon file)
*/
"/(.*?trpc.*?|(?!static|.*\\..*|_next|favicon.ico).*)",
"/"
],
};
export default withClerkMiddleware((request: NextRequest) => {
const { userId ,debug } = getAuth(request);
console.log(debug())
if (isPublic(request.nextUrl.pathname)) {
console.log(pathname)
return NextResponse.next();
}
if (!userId) {
// redirect the users to /pages/sign-in/[[...index]].ts
console.log(request.url)
const signInUrl = new URL("/sign-in", request.url);
signInUrl.searchParams.set("redirect_url", request.url);
return NextResponse.redirect(signInUrl);
}
return NextResponse.next();
});

export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - _next
* - static (static files)
* - favicon.ico (favicon file)
*/
"/(.*?trpc.*?|(?!static|.*\\..*|_next|favicon.ico).*)",
"/"
],
};
Then send me a result.
bozobit
bozobit14mo ago
was too long for discord
James Perkins
James Perkins14mo ago
Okay yeah it's working correctly. is posts.getAll a public route and want to be able to run without a user logged in?
bozobit
bozobit14mo ago
Yes. It's the first simple example in Theo's demo. That's where I got stuck.
James Perkins
James Perkins14mo ago
okay 🙂 caught up now. Okay so!
bozobit
bozobit14mo ago
Add the logic for the private procedure seemed to break it and give me the TRPC fail message that Appstein reported.
James Perkins
James Perkins14mo ago
So the middleware that Theo's uses is this:
import { withClerkMiddleware } from "@clerk/nextjs/server";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export default withClerkMiddleware((req: NextRequest) => {
return NextResponse.next();
});
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - _next
* - static (static files)
* - favicon.ico (favicon file)
*/
"/(.*?trpc.*?|(?!static|.*\\..*|_next|favicon.ico).*)",
"/"
],
};
import { withClerkMiddleware } from "@clerk/nextjs/server";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export default withClerkMiddleware((req: NextRequest) => {
return NextResponse.next();
});
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - _next
* - static (static files)
* - favicon.ico (favicon file)
*/
"/(.*?trpc.*?|(?!static|.*\\..*|_next|favicon.ico).*)",
"/"
],
};
But the one you are using has page protection. which makes everything a bit different. SO the middleware you have you need to do this:
import { getAuth, withClerkMiddleware } from "@clerk/nextjs/server";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";


const publicPaths = ["/", "/sign-in*", "/sign-up*", "/api/trpc/posts.getAll*"];

const isPublic = (path: string) => {
return publicPaths.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);

if (!userId) {
// redirect the users to /pages/sign-in/[[...index]].ts

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
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - _next
* - static (static files)
* - favicon.ico (favicon file)
*/
"/(.*?trpc.*?|(?!static|.*\\..*|_next|favicon.ico).*)",
"/"
],
};
import { getAuth, withClerkMiddleware } from "@clerk/nextjs/server";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";


const publicPaths = ["/", "/sign-in*", "/sign-up*", "/api/trpc/posts.getAll*"];

const isPublic = (path: string) => {
return publicPaths.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);

if (!userId) {
// redirect the users to /pages/sign-in/[[...index]].ts

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
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - _next
* - static (static files)
* - favicon.ico (favicon file)
*/
"/(.*?trpc.*?|(?!static|.*\\..*|_next|favicon.ico).*)",
"/"
],
};
bozobit
bozobit14mo ago
looking at it...
James Perkins
James Perkins14mo ago
basically
const publicPaths = ["/", "/sign-in*", "/sign-up*", "/api/trpc/posts.getAll*"];
const publicPaths = ["/", "/sign-in*", "/sign-up*", "/api/trpc/posts.getAll*"];
You need to add the procedure to your publicPath
bozobit
bozobit14mo ago
Yeah, that line... (just noticed it.) I have many different versions of the trpc.ts and middleware.ts files! Let me sort through this for a few mins. THANK YOU. It's working the public paths and, more important, I think I understand it now. Protecting the API path was not something on my radar! THANK YOU.
James Perkins
James Perkins14mo ago
awesome 👏 glad to help