Failed to get source map when using auth.api.getSession() during first request

Hey there ๐Ÿ‘‹, I am having an issue getting my session on the server in a single request. Currently, it seems that two requests are being made for session data, and as for a brief moment, components accessing session data using useSession() render improperly. Has anyone had this issue before? Where can I begin to debug?
GET /api/auth/session 401 in 78ms
Failed to get source map: [Error: Unknown url scheme] { code: 'GenericFailure' }
GET /api/auth/session?currentURL=http%3A%2F%2Flocalhost%3A3000%2Fpost%2FW38VX0BvSrE 200 in 78ms
GET /api/auth/session 401 in 78ms
Failed to get source map: [Error: Unknown url scheme] { code: 'GenericFailure' }
GET /api/auth/session?currentURL=http%3A%2F%2Flocalhost%3A3000%2Fpost%2FW38VX0BvSrE 200 in 78ms
28 Replies
bekacru
bekacruโ€ข6mo ago
first, are you on latest?
Json ๐Ÿ‘บ
Json ๐Ÿ‘บOPโ€ข6mo ago
Indeed. Edit: Also using NextJs 15.0.2
bekacru
bekacruโ€ข6mo ago
Please try it on the current latest version (0.7.1-beta.1). If the issue still exists, any reproducible steps or more information about your setup would be great.
Json ๐Ÿ‘บ
Json ๐Ÿ‘บOPโ€ข6mo ago
Will do tomorrow. Thanks ๐Ÿ™‚ Alright, after doing a bit of testing, I still can't figure out why the first request is failing, but here are some details that I believe matter to the async useSession issue I have: I have written a getSession() wrapper which I use in my auth layout and in many other places
export async function validateRequest() {
return await auth.api.getSession({
headers: await headers(), // you need to pass the headers object.
});
}
export async function validateRequest() {
return await auth.api.getSession({
headers: await headers(), // you need to pass the headers object.
});
}
When I log the result of this function, the first log shows a good result, so I would expect the useSession client hook to already have this session data, but this is not the case. Log 1 of useSession().session: session data while isPending is true: null (null result) Log 2: session data while isPending is false: Object { session: {โ€ฆ}, user: {โ€ฆ} } (good result) The solution is straight forward. I'll just use an SSR provider that passes the session data got with validateRequest() or the getSession() function itself.
bekacru
bekacruโ€ข6mo ago
This is expected. The useSession hook doesn't cache data from auth.api.getSession so the first re-render the data will always be null. What you can do is pass the session from the server component to the client component and use it as a fallback
const { data: session } = useSession()
const session = session || props.session
const { data: session } = useSession()
const session = session || props.session
Json ๐Ÿ‘บ
Json ๐Ÿ‘บOPโ€ข6mo ago
Good idea. Why not just use an SSR provider in a layout so the data is always available on mount?
bekacru
bekacruโ€ข6mo ago
We didnโ€™t want to implement this type of integration at this stage. Our goal is to keep things consistent across frameworks to minimize maintenance in the integration layer. Also, the types can become complex when handling those kind of integrations with plugins. If you need more control over caching client data, I recommend using authClient.getSession instead of useSession on the client side and use it with a tool like React Query. Also, you could wrap the whole setup in a provider, as you suggested.
bekacru
bekacruโ€ข6mo ago
also you should check cookie cache to avoid querying the db for every getSession calls
Json ๐Ÿ‘บ
Json ๐Ÿ‘บOPโ€ข6mo ago
In react, at least on the server we have the cache() function which prevents duplicate calls per render pass Thanks for the recommendation. I'll get to reading.
bekacru
bekacruโ€ข6mo ago
yeah but you may still need to hit the server multiple times from the client, which is where it could be useful.
Json ๐Ÿ‘บ
Json ๐Ÿ‘บOPโ€ข6mo ago
What about from a server component (layout), passing initial session data into a provider that takes this initialData into React Query and then uses authClient.getSession() as queryFn ? Is this kind of what you meant? This provider just returns the query itself
bekacru
bekacruโ€ข6mo ago
Yes, you could manage the session like that. The useSession hook doesnโ€™t implement a cache, but react query does, so you can benefit from that as well. The only issue is that youโ€™d need to trigger a refetch whenever something that updates the session occurs (like updateUser).
Json ๐Ÿ‘บ
Json ๐Ÿ‘บOPโ€ข6mo ago
Yeah, I'm not sure how to do this... maybe a short staleTime would be fine enough. Alternatively, when we update user, we can use router.refresh() (not ideal)... SSR invalidation would be ideal, but not sure how to wire that up if it's even possible
bekacru
bekacruโ€ข6mo ago
No, if you're using React query you can just use refetch to refetch the session.
Json ๐Ÿ‘บ
Json ๐Ÿ‘บOPโ€ข6mo ago
Oh right
bekacru
bekacruโ€ข6mo ago
You should only use the SSR data as a fallback initially The cookie cache I mentioned simply means that when a request hits the server, it reads from the cookie cache instead of querying the db until the cache expires. Essentially, it works like a stateless session using JWT access tokens.
Json ๐Ÿ‘บ
Json ๐Ÿ‘บOPโ€ข6mo ago
So just enabling this in authClient is enough? Seems pretty nice
session: {
cookieCache: {
enabled: true,
maxAge: 5 * 60, // Cache duration in seconds
},
},
session: {
cookieCache: {
enabled: true,
maxAge: 5 * 60, // Cache duration in seconds
},
},
bekacru
bekacruโ€ข6mo ago
yeah!
Json ๐Ÿ‘บ
Json ๐Ÿ‘บOPโ€ข6mo ago
One last brief question: what is the type called for the return of getSession or useSession data? Do I need to manually create this Session, User composite type?
bekacru
bekacruโ€ข6mo ago
you can infer the type using the helpers provided https://www.better-auth.com/docs/concepts/typescript#infering-types
Typescript | Better Auth
Better Auth typescript integration
Json ๐Ÿ‘บ
Json ๐Ÿ‘บOPโ€ข6mo ago
I suppose I could use ReturnType<typeof getSession> edit: Nvm I already have the Session and User types, but the return type of getSession().data & auth.api.getSession() are some composite of both or null.
bekacru
bekacruโ€ข6mo ago
Yeah since they could return null or the actual session
Json ๐Ÿ‘บ
Json ๐Ÿ‘บOPโ€ข6mo ago
type SessionProviderProps = {
initialData: ReturnType<typeof getSession>;
children: React.ReactNode;
};

const query = useQuery({
queryKey: ["session"],
queryFn: async () => getSession(),
staleTime: Infinity, // Maybe change
initialData: initialData,
});
type SessionProviderProps = {
initialData: ReturnType<typeof getSession>;
children: React.ReactNode;
};

const query = useQuery({
queryKey: ["session"],
queryFn: async () => getSession(),
staleTime: Infinity, // Maybe change
initialData: initialData,
});
Is this appropriate?
bekacru
bekacruโ€ข6mo ago
You shouldn't infer the session type like that. I mean, you can, but you're making the ts server work a bit harder. You can use the provided helper authClient.$Infer.Session | null.
Json ๐Ÿ‘บ
Json ๐Ÿ‘บOPโ€ข6mo ago
This is what is kind of confusing because session is just the Session itself, but the return type of this function is Session and User
bekacru
bekacruโ€ข6mo ago
no $Infer.Session should return a session and a user object as well.
Json ๐Ÿ‘บ
Json ๐Ÿ‘บOPโ€ข6mo ago
Ahhh, sorry about that... you've been very helpful, by the way. My money is on Better-Auth. Definitely all in at this point Here is the provider and hook solution I came up with if you ever consider adding a Next.js specific example to the docs or any other Server Component framework @/components/providers/session-provider.tsx
"use client";

import { useQuery, UseQueryResult } from "@tanstack/react-query";
import { getSession } from "@/lib/auth-client";
import { Session } from "@/lib/auth-client"; // Explicitly inferred type
import React, { useContext } from "react";

type SessionContextType = UseQueryResult<Session | null, Error>;
const SessionContext = React.createContext<SessionContextType | undefined>(
undefined,
);

type SessionProviderProps = {
initialData: Session | null;
children: React.ReactNode;
};

export function SessionProvider({
initialData,
children,
}: SessionProviderProps) {
const query = useQuery({
queryKey: ["session"],
queryFn: async () => {
const result = await getSession();
if (result.error) {
throw new Error(result.error.message || "Failed to fetch session");
}
return result.data;
},
staleTime: Infinity,
initialData: initialData,
});

return (
<SessionContext.Provider value={query}>{children}</SessionContext.Provider>
);
}

export const useSession = (): SessionContextType => {
const sessionContext = useContext(SessionContext);
if (sessionContext === undefined) {
throw new Error("useSession must be used within SessionProvider");
}
return sessionContext;
};
"use client";

import { useQuery, UseQueryResult } from "@tanstack/react-query";
import { getSession } from "@/lib/auth-client";
import { Session } from "@/lib/auth-client"; // Explicitly inferred type
import React, { useContext } from "react";

type SessionContextType = UseQueryResult<Session | null, Error>;
const SessionContext = React.createContext<SessionContextType | undefined>(
undefined,
);

type SessionProviderProps = {
initialData: Session | null;
children: React.ReactNode;
};

export function SessionProvider({
initialData,
children,
}: SessionProviderProps) {
const query = useQuery({
queryKey: ["session"],
queryFn: async () => {
const result = await getSession();
if (result.error) {
throw new Error(result.error.message || "Failed to fetch session");
}
return result.data;
},
staleTime: Infinity,
initialData: initialData,
});

return (
<SessionContext.Provider value={query}>{children}</SessionContext.Provider>
);
}

export const useSession = (): SessionContextType => {
const sessionContext = useContext(SessionContext);
if (sessionContext === undefined) {
throw new Error("useSession must be used within SessionProvider");
}
return sessionContext;
};
../layout.tsx
import { SessionProvider } from "./session-provider";
import { auth } from "@/lib/auth";
import { headers } from "next/headers";

export default async function Providers({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const session = await auth.api.getSession({
headers: await headers(),
});

return (
<>
...
<SessionProvider initialData={session}>{children}</SessionProvider>
</>
);
}
import { SessionProvider } from "./session-provider";
import { auth } from "@/lib/auth";
import { headers } from "next/headers";

export default async function Providers({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const session = await auth.api.getSession({
headers: await headers(),
});

return (
<>
...
<SessionProvider initialData={session}>{children}</SessionProvider>
</>
);
}

Did you find this page helpful?