Auth: sign-in and sign-out load a new document - #77
Merged
Merged
Conversation
Signing out ran router.push("/login") then router.refresh(). Next runs
the queued refresh before React commits /login, and the refresh
re-prefetches every link still mounted: the sidebar's prefetches went
out with the session cookie already cleared, so the (dashboard) layout
rendered signed out and its redirect("/login") was cached as a complete
layout segment, shared by every dashboard route, for the 180 s static
stale time. The next sign-in's router.push(next) reused it and bounced
back to /login; past the 30 s dynamic stale time it looped
/emails -> /login -> / -> /emails on a blank screen until the entry
expired. A session that expired on /onboarding cached the same entry.
Sign-in now replaces the location with next, and sign-out (dashboard
and console menus, through signOutToLogin) loads /login, so no router
payload, react-query data or language provider from before the change
survives it. Sign-out asks about unsaved editor changes before the
session ends, and leaveDocument keeps the editor's beforeunload prompt
from asking again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
safeNextPath resolved dot segments before returning the path, so "/.//host" came back as "//host", which a browser reads as another origin: the sign-in form and verify-email's redirect for a signed-in visitor could leave the app. A normalized path that starts with "//" now falls back like any other off-site target. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Signing in with email and password sometimes left the user on the login form, and a reload got them in. Signing in a little later instead looped on a blank page. Both came from the client router cache. Sign-in and sign-out now load a new document.
Cause
router.push("/login")and thenrouter.refresh(). Next 16 runs the queued refresh before React commits /login, and the refresh re-prefetches every<Link>that is still mounted. The sidebar's prefetches went out with the session cookie already cleared. So(dashboard)/layout.tsxrendered signed out, and itsredirect("/login")was cached as a complete layout segment. That one entry is shared by every dashboard route and lives forstaleTimes.static(180 s).router.push(next). That reused the cached layout, andRedirectBoundaryreplayed the redirect:/,/redirected to/emails, and/emailsreplayed the cached redirect. That made an endless loop on a blank screen until the entry expired.next devnever prefetches on viewport or hover.Change
auth-form.tsx: after a successful sign-in, or a sign-up that returns a session,window.location.replace(next). The target loads as a new document with empty client caches, the root layout renders in the account's language (NEXT_LOCALEis set by the sign-in), and Back skips the spent form.signOutToLogin()inlib/auth-client.ts: used by the dashboard and console menus. It signs out, then loads /login as a new document, so the account's router payloads and react-query data do not outlive its session in the tab.confirmUnsavedNavigation(), so Cancel keeps the session.leaveDocument()stops the editor's beforeunload prompt from asking a second time.next.config.ts: thestaleTimescomment no longer claims cached payloads hold no session data.?next=dot segments (second commit)safeNextPathcompared the parsed origin and then returned the normalized path. That let/.//hostcome back as//host, which a browser resolves to another origin. The redirect happens after email sign-in, and in/verify-email's redirect for a visitor who is already signed in. A normalized path that starts with//now falls back like any other off-site target, andnav.test.tslists the forms.Verification
Local production build (
next build+next start, Postgres 17), driven by headless Chromium, with every RSC request and response body logged. The new scenarios were also run against a pre-fix build of the same tree as a control.?next=/domainstsc, Biome and the web suite (839 tests) pass. The full browser suite above was rerun on the committed tree (19/19).Not in this change
Server-side
getSessioncalls drop the cookie that better-auth re-issues when it extends a session. So the browser's session cookie expires 7 days after sign-in regardless of activity, while the database row keeps sliding. That is a separate fix.🤖 Generated with Claude Code