diff --git a/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx b/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx index 6ddcd97e4..8a2e6c581 100644 --- a/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx +++ b/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx @@ -17,7 +17,7 @@ import { import superjson from "superjson"; import type { AppRouter } from "@homarr/api"; -import { clientApi } from "@homarr/api/client"; +import { clientApi, createHeadersCallbackForSource, getTrpcUrl } from "@homarr/api/client"; import { env } from "~/env.mjs"; @@ -86,16 +86,13 @@ export function TRPCReactProvider(props: PropsWithChildren) { return data; }, }, - url: `${getBaseUrl()}/api/trpc`, + url: getTrpcUrl(), + headers: createHeadersCallbackForSource("nextjs-react (form-data)"), }), false: unstable_httpBatchStreamLink({ transformer: superjson, - url: `${getBaseUrl()}/api/trpc`, - headers() { - const headers = new Headers(); - headers.set("x-trpc-source", "nextjs-react"); - return headers; - }, + url: getTrpcUrl(), + headers: createHeadersCallbackForSource("nextjs-react (json)"), }), }), }), @@ -112,9 +109,3 @@ export function TRPCReactProvider(props: PropsWithChildren) { ); } - -function getBaseUrl() { - if (typeof window !== "undefined") return window.location.origin; - if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; - return `http://localhost:${process.env.PORT ?? 3000}`; -} diff --git a/packages/api/src/client.ts b/packages/api/src/client.ts index 0d7c95f38..b40cdd412 100644 --- a/packages/api/src/client.ts +++ b/packages/api/src/client.ts @@ -7,13 +7,9 @@ export const clientApi = createTRPCReact(); export const fetchApi = createTRPCClient({ links: [ httpLink({ - url: `${getBaseUrl()}/api/trpc`, + url: getTrpcUrl(), transformer: SuperJSON, - headers() { - const headers = new Headers(); - headers.set("x-trpc-source", "fetch"); - return headers; - }, + headers: createHeadersCallbackForSource("fetch"), }), ], }); @@ -23,3 +19,50 @@ function getBaseUrl() { if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; return `http://localhost:${process.env.PORT ?? 3000}`; } + +/** + * Creates the full url for the trpc api endpoint + * @returns + */ +export function getTrpcUrl() { + return `${getBaseUrl()}/api/trpc`; +} + +/** + * Creates a headers callback for a given source + * It will set the x-trpc-source header and cookies if needed + * @param source trpc source request comes from + * @returns headers callback + */ +export function createHeadersCallbackForSource(source: string) { + return async () => { + const headers = new Headers(); + headers.set("x-trpc-source", source); + + const cookies = await importCookiesAsync(); + // We need to set cookie for ssr requests (for example with useSuspenseQuery or middleware) + if (cookies) { + headers.set("cookie", cookies); + } + + return headers; + }; +} + +/** + * This is a workarround as cookies are not passed to the server + * when using useSuspenseQuery or middleware + * @returns cookie string on server or null on client + */ +async function importCookiesAsync() { + if (typeof window === "undefined") { + return await import("next/headers").then(({ cookies }) => + cookies() + .getAll() + .map(({ name, value }) => `${name}=${value}`) + .join(";"), + ); + } + + return null; +}