Stale client session data after server action login in Next #2282

Open
opened 2026-03-13 09:40:59 -05:00 by GiteaMirror · 4 comments
Owner

Originally created by @Romflz on GitHub (Nov 9, 2025).

Originally assigned to: @Paola3stefania on GitHub.

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

To recreate:

  1. Initialize new Next16 project
  2. Configure better auth with nextjs as per better auth docs. Create better auth client with createAuthClient()
  3. Log user client session in the console
  4. Initialize login of user A via a server action
  5. Log user A out via a server action
  6. Log user B in via a server action
  7. Client session will log user A session

Current vs. Expected behavior

Expected behavior:
Client user session refresh after new user logins in

Current behavior:
Client session still has previous session

What version of Better Auth are you using?

1.3.34

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.0.0: Wed Sep 17 21:41:50 PDT 2025; root:xnu-12377.1.9~141/RELEASE_ARM64_T6030",
    "release": "25.0.0",
    "cpuCount": 11,
    "cpuModel": "Apple M3 Pro",
    "totalMemory": "18.00 GB",
    "freeMemory": "0.39 GB"
  },
  "node": {
    "version": "v22.20.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "10.9.3"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "16.0.1"
    },
    {
      "name": "react",
      "version": "19.2.0"
    }
  ],
  "databases": [
    {
      "name": "pg",
      "version": "^8.16.3"
    },
    {
      "name": "@prisma/client",
      "version": "^6.19.0"
    }
  ],
  "betterAuth": {
    "version": "^1.3.34",
    "config": {
      "emailAndPassword": {
        "enabled": true,
        "requireEmailVerification": false,
        "sendOnSignUp": false
      },
      "user": {
        "changeEmail": {
          "enabled": true
        }
      },
      "emailVerification": {},
      "socialProviders": {
        "google": {
          "clientId": "[REDACTED]",
          "clientSecret": "[REDACTED]"
        }
      },
      "plugins": [
        {
          "name": "next-cookies",
          "config": {
            "id": "next-cookies",
            "hooks": {
              "after": [
                {}
              ]
            }
          }
        }
      ]
    }
  }
}

Which area(s) are affected? (Select all that apply)

Client

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
  emailAndPassword: {  
    enabled: true
  },
});

Additional context

No response

Originally created by @Romflz on GitHub (Nov 9, 2025). Originally assigned to: @Paola3stefania on GitHub. ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce To recreate: 1. Initialize new Next16 project 2. Configure better auth with nextjs as per better auth docs. Create better auth client with createAuthClient() 3. Log user client session in the console 4. Initialize login of user A via a server action 5. Log user A out via a server action 6. Log user B in via a server action 7. Client session will log user A session ### Current vs. Expected behavior Expected behavior: Client user session refresh after new user logins in Current behavior: Client session still has previous session ### What version of Better Auth are you using? 1.3.34 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.0.0: Wed Sep 17 21:41:50 PDT 2025; root:xnu-12377.1.9~141/RELEASE_ARM64_T6030", "release": "25.0.0", "cpuCount": 11, "cpuModel": "Apple M3 Pro", "totalMemory": "18.00 GB", "freeMemory": "0.39 GB" }, "node": { "version": "v22.20.0", "env": "development" }, "packageManager": { "name": "npm", "version": "10.9.3" }, "frameworks": [ { "name": "next", "version": "16.0.1" }, { "name": "react", "version": "19.2.0" } ], "databases": [ { "name": "pg", "version": "^8.16.3" }, { "name": "@prisma/client", "version": "^6.19.0" } ], "betterAuth": { "version": "^1.3.34", "config": { "emailAndPassword": { "enabled": true, "requireEmailVerification": false, "sendOnSignUp": false }, "user": { "changeEmail": { "enabled": true } }, "emailVerification": {}, "socialProviders": { "google": { "clientId": "[REDACTED]", "clientSecret": "[REDACTED]" } }, "plugins": [ { "name": "next-cookies", "config": { "id": "next-cookies", "hooks": { "after": [ {} ] } } } ] } } } ``` ### Which area(s) are affected? (Select all that apply) Client ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ emailAndPassword: { enabled: true }, }); ``` ### Additional context _No response_
GiteaMirror added the nextjsbug labels 2026-03-13 09:40:59 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Nov 9, 2025):

Hi @Romflz! I'm Dosu and I’m helping the better-auth team.

This is a known limitation with Better Auth and Next.js: after server actions like login/logout, the client session (from useSession or createAuthClient) does not automatically update, so you see stale session data until a manual refresh or refetch. This happens because server actions update cookies and session state on the server, but the client-side hooks aren't notified and don't refetch the session unless triggered explicitly. Social logins work because they cause a full page reload, which naturally refreshes the session state reference.

To work around this, you can trigger a client-side session refresh after server actions. For example, after a server action completes, call the refetch method from the useSession hook, or use a client component that calls authClient.getSession() or authClient.useSession() on navigation or after login/logout reference. Some teams place a SessionRefresher component in the root layout to keep the session in sync:

'use client';
import { authClient } from '@/lib/auth-client';

export function SessionRefresher() {
  authClient.useSession();
  return null;
}

You can also trigger a refresh on navigation changes:

'use client';
import { usePathname } from 'next/navigation';
import { useEffect } from 'react';
import { authClient } from '@/lib/auth-client';

export function SessionRefresher() {
  const pathname = usePathname();
  useEffect(() => {
    authClient.getSession();
  }, [pathname]);
  return null;
}

If you want to avoid manual refreshes, consider polling or refetching the session on tab focus or visibility change. There is no built-in push mechanism from server to client in Better Auth yet, but recent updates have added session refresh managers that help keep the session in sync across tabs and browser events reference.

If you use custom cookie settings, make sure cookies are set with the correct domain, path, and secure flags, and that you aren't using partitioned cookies unless required reference.

Let me know if you need a more specific code example for your flow!

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

@dosubot[bot] commented on GitHub (Nov 9, 2025): <!-- Greeting --> Hi @Romflz! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> This is a known limitation with Better Auth and Next.js: after server actions like login/logout, the client session (from `useSession` or `createAuthClient`) does not automatically update, so you see stale session data until a manual refresh or refetch. This happens because server actions update cookies and session state on the server, but the client-side hooks aren't notified and don't refetch the session unless triggered explicitly. Social logins work because they cause a full page reload, which naturally refreshes the session state [reference](https://github.com/better-auth/better-auth/issues/3608). To work around this, you can trigger a client-side session refresh after server actions. For example, after a server action completes, call the `refetch` method from the `useSession` hook, or use a client component that calls `authClient.getSession()` or `authClient.useSession()` on navigation or after login/logout [reference](https://github.com/better-auth/better-auth/issues/2115). Some teams place a `SessionRefresher` component in the root layout to keep the session in sync: ```tsx 'use client'; import { authClient } from '@/lib/auth-client'; export function SessionRefresher() { authClient.useSession(); return null; } ``` You can also trigger a refresh on navigation changes: ```tsx 'use client'; import { usePathname } from 'next/navigation'; import { useEffect } from 'react'; import { authClient } from '@/lib/auth-client'; export function SessionRefresher() { const pathname = usePathname(); useEffect(() => { authClient.getSession(); }, [pathname]); return null; } ``` If you want to avoid manual refreshes, consider polling or refetching the session on tab focus or visibility change. There is no built-in push mechanism from server to client in Better Auth yet, but recent updates have added session refresh managers that help keep the session in sync across tabs and browser events [reference](https://github.com/better-auth/better-auth/pull/5630). If you use custom cookie settings, make sure cookies are set with the correct domain, path, and secure flags, and that you aren't using partitioned cookies unless required [reference](https://github.com/better-auth/better-auth/issues/3267). Let me know if you need a more specific code example for your flow! <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/6b46e04e-9c69-4531-b6a4-bf0a4c51f097?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/6b46e04e-9c69-4531-b6a4-bf0a4c51f097?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/6b46e04e-9c69-4531-b6a4-bf0a4c51f097?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/6b46e04e-9c69-4531-b6a4-bf0a4c51f097?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/6b46e04e-9c69-4531-b6a4-bf0a4c51f097?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/6b46e04e-9c69-4531-b6a4-bf0a4c51f097?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/6b46e04e-9c69-4531-b6a4-bf0a4c51f097?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/5875)
Author
Owner

@rufique commented on GitHub (Nov 13, 2025):

Well, my case is different, after logout, or even a fresh login on a different browser, i get the wrong session, although sometimes it is correct, it is unpredictable. My logout method is removing the cookie on the client, however get_session on the client returns the old cookie even if the user has changed

"use client";
import { usePathname } from "next/navigation";
import { useEffect, useRef } from "react";
import { authClient } from "~/lib/auth-client";

export function SessionRefresher() {
  const pathname = usePathname();
  const lastPathRef = useRef(pathname);

  useEffect(() => {
    // Force refresh session when pathname changes
    if (pathname !== lastPathRef.current) {
      lastPathRef.current = pathname;
      authClient.getSession({
        fetchOptions: {
          cache: "no-cache"
        }
      });
    }
  }, [pathname]);

  useEffect(() => {
    // Initial session fetch on mount with cache bypass
    authClient.getSession({
      fetchOptions: {
        cache: "no-cache"
      }
    });

    // Refresh session when tab becomes visible again
    const handleVisibilityChange = () => {
      if (document.visibilityState === "visible") {
        authClient.getSession({
          fetchOptions: {
            cache: "no-cache"
          }
        });
      }
    };

    // Refresh session when window regains focus
    const handleFocus = () => {
      authClient.getSession({
        fetchOptions: {
          cache: "no-cache"
        }
      });
    };

    document.addEventListener("visibilitychange", handleVisibilityChange);
    window.addEventListener("focus", handleFocus);

    return () => {
      document.removeEventListener("visibilitychange", handleVisibilityChange);
      window.removeEventListener("focus", handleFocus);
    };
  }, []);

  return null;
}

I have added this method, but the issue is persitent

@rufique commented on GitHub (Nov 13, 2025): Well, my case is different, after logout, or even a fresh login on a different browser, i get the wrong session, although sometimes it is correct, it is unpredictable. My logout method is removing the cookie on the client, however get_session on the client returns the old cookie even if the user has changed ``` "use client"; import { usePathname } from "next/navigation"; import { useEffect, useRef } from "react"; import { authClient } from "~/lib/auth-client"; export function SessionRefresher() { const pathname = usePathname(); const lastPathRef = useRef(pathname); useEffect(() => { // Force refresh session when pathname changes if (pathname !== lastPathRef.current) { lastPathRef.current = pathname; authClient.getSession({ fetchOptions: { cache: "no-cache" } }); } }, [pathname]); useEffect(() => { // Initial session fetch on mount with cache bypass authClient.getSession({ fetchOptions: { cache: "no-cache" } }); // Refresh session when tab becomes visible again const handleVisibilityChange = () => { if (document.visibilityState === "visible") { authClient.getSession({ fetchOptions: { cache: "no-cache" } }); } }; // Refresh session when window regains focus const handleFocus = () => { authClient.getSession({ fetchOptions: { cache: "no-cache" } }); }; document.addEventListener("visibilitychange", handleVisibilityChange); window.addEventListener("focus", handleFocus); return () => { document.removeEventListener("visibilitychange", handleVisibilityChange); window.removeEventListener("focus", handleFocus); }; }, []); return null; } ``` I have added this method, but the issue is persitent
Author
Owner

@Romflz commented on GitHub (Nov 13, 2025):

What I did to bypass the issue is create a SessionRefresher.tsx component that sits at my layout. As how the bot suggested. This is the code:

'use client'
import { useEffect } from 'react'
import { authClient } from '@/lib/auth-client'

export default function SessionFocusSync() {
  const { refetch } = authClient.useSession()
  useEffect(() => {
    const onFocus = () => refetch()
    window.addEventListener('focus', onFocus)
    return () => window.removeEventListener('focus', onFocus)
  }, [refetch])
  return null
}

Not sure if this is 100% correct way, but it seems to work in my case. (In fact, after creating an after hook in the auth, as per docs https://www.better-auth.com/docs/concepts/hooks, I noticed that the refresh session calls the get-session, which is good)

I also converted my login and register to use the client api instead of server actions due to this issue and also when reading the docs:
Server-side requests made using auth.api aren't affected by rate limiting. Rate limits only apply to client-initiated requests. states that rate limiting only works on client api.

I have also encountered a bug where when a user has a session saved, and you delete the said user from the db, the client does not know what to do, so if you have middleware that checks the cookie, make sure it also checks if its valid and removes it if its not. As the server does not remove it automatically. (Need to fully test this later, as its been hit or miss)

@Romflz commented on GitHub (Nov 13, 2025): What I did to bypass the issue is create a SessionRefresher.tsx component that sits at my layout. As how the bot suggested. This is the code: ``` 'use client' import { useEffect } from 'react' import { authClient } from '@/lib/auth-client' export default function SessionFocusSync() { const { refetch } = authClient.useSession() useEffect(() => { const onFocus = () => refetch() window.addEventListener('focus', onFocus) return () => window.removeEventListener('focus', onFocus) }, [refetch]) return null } ``` Not sure if this is 100% correct way, but it seems to work in my case. (In fact, after creating an after hook in the auth, as per docs https://www.better-auth.com/docs/concepts/hooks, I noticed that the refresh session calls the get-session, which is good) I also converted my login and register to use the client api instead of server actions due to this issue and also when reading the docs: `Server-side requests made using auth.api aren't affected by rate limiting. Rate limits only apply to client-initiated requests. ` states that rate limiting only works on client api. I have also encountered a bug where when a user has a session saved, and you delete the said user from the db, the client does not know what to do, so if you have middleware that checks the cookie, make sure it also checks if its valid and removes it if its not. As the server does not remove it automatically. (Need to fully test this later, as its been hit or miss)
Author
Owner

@Paola3stefania commented on GitHub (Jan 9, 2026):

@Romflz you still have this issue with latest version?

@Paola3stefania commented on GitHub (Jan 9, 2026): @Romflz you still have this issue with latest version?
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2282