'Invalid response or stream interrupted' when using T3+TRPC with Clerk

Hello, I've encountered strange error when trying to use trpc's useSuspenseQuery while having clerk authentication implemented. When I try to call useSuspenseQuery, I get Invalid response or stream interrupted error. On client I get Uncaught Switched to client rendering because the server rendering errored. The full terminal output can be seen in this Github Gist. As I was putting together minimal reproducible example, I found out that this doesn't occur without Clerk. This led me to suspect it has something to do with Clerk's middleware, but I have no idea how to even start to debug this. Here is the minimal reproducible example: - Github repo - StackBlitz You'll need to add NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY to .env to run this. Strangely, if I add api.[route].prefetch() to server component for all of the calls, this error doesn't occur. In the example I shown why this is not suitable solution. I first load posts, and based on post data I load author data. To prefetch data structured like this, I'd have to await the posts query, to then be able to prefetch author data. This is not ideal, as it blocks first render. Does anyone know what is this about? Or where to even begin to look to debug this?
1 Reply
realS3BI
realS3BI2mo ago
Hi. I don't know if it is too late... (and for everybody that is facing the same issue in the future)... But I had the same problem, and it was solved by enter the same input in the prefetch and in the suspense query... See here my "limit" input... Here is my code:
// bills.jsx
"use client";

import { api } from "~/trpc/react";

export function BillsList() {
const [bills] = api.bill.getBills.useSuspenseQuery({ limit: 10 });

if (!bills || bills.length === 0) {
return <div>No Bills Found</div>;
}

return (
<ul>
{bills.map((bill) => (
<li key={bill.id}>{bill.sum}</li>
))}
</ul>
);
}

// page.jsx
import React from "react";

import { BillsList } from "./bills";
import { auth } from "~/server/auth";
import { api, HydrateClient } from "~/trpc/server";

export default async function Test2Page() {
const session = await auth();

if (session?.user) {
void api.bill.getBills.prefetch({ limit: 10 });
}

return (
<HydrateClient>
<div>
<p>Show all Bills here.</p>
<hr />
{session?.user && (
<React.Suspense fallback={<div>Loading...</div>}>
<BillsList />
</React.Suspense>
)}
<hr />
</div>
</HydrateClient>
);
}
// bills.jsx
"use client";

import { api } from "~/trpc/react";

export function BillsList() {
const [bills] = api.bill.getBills.useSuspenseQuery({ limit: 10 });

if (!bills || bills.length === 0) {
return <div>No Bills Found</div>;
}

return (
<ul>
{bills.map((bill) => (
<li key={bill.id}>{bill.sum}</li>
))}
</ul>
);
}

// page.jsx
import React from "react";

import { BillsList } from "./bills";
import { auth } from "~/server/auth";
import { api, HydrateClient } from "~/trpc/server";

export default async function Test2Page() {
const session = await auth();

if (session?.user) {
void api.bill.getBills.prefetch({ limit: 10 });
}

return (
<HydrateClient>
<div>
<p>Show all Bills here.</p>
<hr />
{session?.user && (
<React.Suspense fallback={<div>Loading...</div>}>
<BillsList />
</React.Suspense>
)}
<hr />
</div>
</HydrateClient>
);
}
Here is also the billRouter:
export const billRouter = createTRPCRouter({
getBills: protectedProcedure
.input(
z.object({
teamIds: z.array(z.string()).optional(),
createdAt: z
.object({
from: z.string().optional(),
to: z.string().optional(),
})
.optional(),
offset: z.number().optional(),
limit: z.number().optional(),
}),
)
.query(async ({ ctx, input }) => {
const { teamIds, createdAt, offset, limit } = input;
await new Promise((resolve) => setTimeout(resolve, 2000)); // Simulate Longer waiting time for Suspense
const result = await ctx.db.query.bills.findMany({
where: (bills, { inArray, and, gte, lte }) =>
and(
teamIds ? inArray(bills.teamId, teamIds) : undefined,
createdAt?.from
? gte(bills.createdAt, new Date(createdAt.from))
: undefined,
createdAt?.to
? lte(bills.createdAt, new Date(createdAt.to))
: undefined,
),
offset,
limit,
});

return result ?? null;
}),
});
export const billRouter = createTRPCRouter({
getBills: protectedProcedure
.input(
z.object({
teamIds: z.array(z.string()).optional(),
createdAt: z
.object({
from: z.string().optional(),
to: z.string().optional(),
})
.optional(),
offset: z.number().optional(),
limit: z.number().optional(),
}),
)
.query(async ({ ctx, input }) => {
const { teamIds, createdAt, offset, limit } = input;
await new Promise((resolve) => setTimeout(resolve, 2000)); // Simulate Longer waiting time for Suspense
const result = await ctx.db.query.bills.findMany({
where: (bills, { inArray, and, gte, lte }) =>
and(
teamIds ? inArray(bills.teamId, teamIds) : undefined,
createdAt?.from
? gte(bills.createdAt, new Date(createdAt.from))
: undefined,
createdAt?.to
? lte(bills.createdAt, new Date(createdAt.to))
: undefined,
),
offset,
limit,
});

return result ?? null;
}),
});

Did you find this page helpful?