IceAge2OnDVD
IceAge2OnDVD11mo ago

Next cookies() not being set on Mutation in App Dir (T3 Turbo)

Trying to set cookies inside TRPC using Nextjs App Router, using the T3 Turbo setup. Setting cookies works fine inside Queries, but cookies are not set inside Mutations. Maybe I'm doing something wrong, not very up to date with TRPC + App Dir, or it could be a bug in next, not sure.
export const postRouter = createTRPCRouter({
all: publicProcedure.query(({ ctx }) => {
cookies().set("Cookies work on QUERY", "yes")
return [{id:0,
title: "query test",
content: "a test for trpc query",
createdAt: Date.now(),
updatedAt: Date.now()}]
}),

create: publicProcedure
.input(
z.object({
title: z.string().min(1),
content: z.string().min(1),
}),
)
.mutation(({ ctx, input }) => {
cookies().set("Cookies work on MUTATION", "No!")
console.log(cookies().getAll())
return 200;
}),
});
export const postRouter = createTRPCRouter({
all: publicProcedure.query(({ ctx }) => {
cookies().set("Cookies work on QUERY", "yes")
return [{id:0,
title: "query test",
content: "a test for trpc query",
createdAt: Date.now(),
updatedAt: Date.now()}]
}),

create: publicProcedure
.input(
z.object({
title: z.string().min(1),
content: z.string().min(1),
}),
)
.mutation(({ ctx, input }) => {
cookies().set("Cookies work on MUTATION", "No!")
console.log(cookies().getAll())
return 200;
}),
});
After calling both the query and the mutation on frontend you only get the cookie from the Query. But this gets logged to console.
@acme/nextjs:dev: [
@acme/nextjs:dev: { name: 'Cookies work on QUERY', value: 'yes', path: '/' },
@acme/nextjs:dev: { name: 'Cookies work on MUTATION', value: 'No!', path: '/' }
@acme/nextjs:dev: ]
@acme/nextjs:dev: [
@acme/nextjs:dev: { name: 'Cookies work on QUERY', value: 'yes', path: '/' },
@acme/nextjs:dev: { name: 'Cookies work on MUTATION', value: 'No!', path: '/' }
@acme/nextjs:dev: ]
(this is after calling the query first then the mutation.)
9 Replies
IceAge2OnDVD
IceAge2OnDVD10mo ago
Sorry for thing ping but I feel like I may need some more expert help on this @marminge I though that it may be something to do with not being able to set cookies after streaming starts, but then it doesn’t make sense how cookies work in query but not mutation At least it doesn’t make sense to me
_jdow
_jdow10mo ago
I’m also struggling with the same issue in app router. I found that it’s actually the .input() that breaks cookies from settings correctly. Both query or mutation sets cookies properly with a hardcoded value and no .input() but as soon as you add the .input() (even if you still use a hardcoded value for the cookie) it doesn’t get set Really hoping to find a solution, but don't really know where to go from here 😢
IceAge2OnDVD
IceAge2OnDVD10mo ago
Is there on open issue for this?
_jdow
_jdow10mo ago
I haven't had too much time to look into it, but not that I could find Probably a good idea to open one about it
mattddean
mattddean10mo ago
It seems like unstable_httpBatchStreamLink has this problem, but httpBatchLink does not
Lucas
Lucas10mo ago
If you're using unstable_httpBatchStreamLink, headers are returned at the beginning of the request (since the response is a streamed response). This means you can't set any headers or cookies inside procedures. httpBatchLink does not have this issue due to the response not being streamed.
IceAge2OnDVD
IceAge2OnDVD10mo ago
Ahhh i see makes sense is there a way you could make it so that certain mutations are never streamed? Like use a regular http link for certain routes
IceAge2OnDVD
IceAge2OnDVD10mo ago
https://trpc.io/docs/client/links/splitLink , i'm going to have a look at using split links, see if that works
Split Link | tRPC
splitLink is a link that allows you to branch your link chain's execution depending on a given condition. Both the true and false branches are required. You can provide just one link, or multiple links per branch via an array.
IceAge2OnDVD
IceAge2OnDVD10mo ago
Managed to get it working, basically the answer is to avoid batchStreamLink on any routes/paths that need to set cookies.
export function TRPCReactProvider(props: {
children: React.ReactNode;
headers: Headers; // <-- Important
}) {
const [queryClient] = useState(() => new QueryClient());

const [trpcClient] = useState(() =>
api.createClient({
transformer,
links: [
unauthenticatedHandler,
loggerLink({
enabled: (opts) =>
process.env.NODE_ENV === "development" ||
(opts.direction === "down" && opts.result instanceof Error),
}),
splitLink({
condition(op) {
// Add logic here to return true for paths/routes that need to set cookies
return op.path.startsWith("auth.");
},
true: httpBatchLink({
url: getUrl(),
headers() {
const heads = new Map(props.headers);
heads.set("x-trpc-source", "react-no-stream");
return Object.fromEntries(heads);
},
}),
false: unstable_httpBatchStreamLink({
url: getUrl(),
headers() {
const heads = new Map(props.headers);
heads.set("x-trpc-source", "react-stream");
return Object.fromEntries(heads);
},
}),
}),
],
}),
);
export function TRPCReactProvider(props: {
children: React.ReactNode;
headers: Headers; // <-- Important
}) {
const [queryClient] = useState(() => new QueryClient());

const [trpcClient] = useState(() =>
api.createClient({
transformer,
links: [
unauthenticatedHandler,
loggerLink({
enabled: (opts) =>
process.env.NODE_ENV === "development" ||
(opts.direction === "down" && opts.result instanceof Error),
}),
splitLink({
condition(op) {
// Add logic here to return true for paths/routes that need to set cookies
return op.path.startsWith("auth.");
},
true: httpBatchLink({
url: getUrl(),
headers() {
const heads = new Map(props.headers);
heads.set("x-trpc-source", "react-no-stream");
return Object.fromEntries(heads);
},
}),
false: unstable_httpBatchStreamLink({
url: getUrl(),
headers() {
const heads = new Map(props.headers);
heads.set("x-trpc-source", "react-stream");
return Object.fromEntries(heads);
},
}),
}),
],
}),
);