Skip to content

feat: getSession on root beforeLoad to avoid spinner#740

Open
HugoPerard wants to merge 6 commits into
mainfrom
improve-session-loader
Open

feat: getSession on root beforeLoad to avoid spinner#740
HugoPerard wants to merge 6 commits into
mainfrom
improve-session-loader

Conversation

@HugoPerard
Copy link
Copy Markdown
Member

@HugoPerard HugoPerard commented Apr 22, 2026

During first app load or after refreshing page we had a full spinner caused by guard components session fetch. This loading state combined with other loading states can be cause some visual shifts. The idea is to fetch beforeLoad the session and to use it on guard (and other session usages)

Before

Screen.Recording.2026-04-22.at.17.15.39.mov

After

Screen.Recording.2026-04-22.at.17.15.19.mov

Summary by CodeRabbit

  • New Features

    • Server-side session initialization for faster initial loads and smoother SSR behavior.
    • Centralized session handling to reduce loading flashes across the app.
  • Bug Fixes

    • Theme switcher now handles unset theme values and hydration edge cases more gracefully.
  • Changes

    • Logout now redirects to the login page.
    • After creating a user, navigation goes to the users list.

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
start-ui-web-v3 Ready Ready Preview, Comment May 7, 2026 9:24am

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 22, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Consolidates session sourcing by introducing a central useSession hook that leverages SSR-prefetched auth via initAuthSsr, replaces direct authClient.useSession() imports across the app, updates root/index route guards to use SSR auth, and applies small UI tweaks (theme null-safety and hydration removal).

Changes

Session / SSR Consolidation

Layer / File(s) Summary
Data Shape / Server fn
src/features/auth/session.ts
Adds initAuthSsr server function returning { authSession } and exports AuthSession type.
SSR Route API
src/routes/__root.tsx
Adds beforeLoad to root that returns { authSession } (from initAuthSsr) and exports RootRouteApi.
Client hook
src/features/auth/use-session.ts
Adds useSession hook that wraps authClient.useSession() and, when client session is pending but authSession exists in route context, returns a non-pending session seeded from SSR payload.
Wiring (imports)
Multiple files (src/features/auth/*, src/features/account/*, src/features/user/*, src/layout/*, src/features/demo/*, etc.)
Switches imports/usages from authClient.useSession() to useSession() across guards, pages, components, and utilities.
Behavioral / Routing
src/routes/index.tsx
Home route beforeLoad converted to async guard using initAuthSsr() to decide redirect to /login, /manager, or /app based on authSession and role permissions.
Tests / Docs
(none in diff)

Theme & UI tweaks

Layer / File(s) Summary
Hydration removal
src/components/ui/theme-switcher.tsx
Removes useHydrated dependency and the hydration-gate early return so component renders immediately.
Null-safety for theme
src/components/ui/theme-switcher.tsx, src/layout/manager/nav-user.tsx
Default theme to 'system' in match and component value={theme ?? 'system'} to avoid falsy/undefined theme causing missing icon/label.
Wiring / UI
src/layout/manager/nav-user.tsx
Uses updated theme handling in dropdown radio group and icon selection.
Tests / Docs
(none in diff)

Small navigation change

Layer / File(s) Summary
Import / Hook change
src/features/user/manager/page-user-new.tsx
Replaced useNavigateBack import with TanStack Router useRouter.
Behavioral
src/features/user/manager/page-user-new.tsx
On successful user creation, navigate explicitly to /manager/users via router.navigate(...) instead of previous back-navigation.
Tests / Docs
(none in diff)

Sequence Diagram(s)

sequenceDiagram
    participant Browser as Browser (client)
    participant Route as Route beforeLoad (__root__)
    participant SSR as initAuthSsr (server fn)
    participant AuthAPI as auth.api.getSession
    participant RouteCtx as RootRouteApi / route context
    Browser->>Route: initial navigation (SSR)
    Route->>SSR: call initAuthSsr()
    SSR->>AuthAPI: auth.api.getSession (uses request headers)
    AuthAPI-->>SSR: returns authSession
    SSR-->>Route: { authSession }
    Route-->>RouteCtx: expose authSession via route context
    Browser->>useSession: client hook call
    useSession->>RouteCtx: read authSession
    useSession-->>Browser: return seeded session (if client pending)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

v3

Suggested reviewers

  • ivan-dalmet
  • ntatoud
  • yoannfleurydev
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: getSession on root beforeLoad to avoid spinner' is concise and accurately reflects the main architectural change in the PR: moving session fetching to the root route's beforeLoad hook to eliminate the spinner on initial load.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch improve-session-loader

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/components/ui/theme-switcher.tsx (1)

38-48: ⚠️ Potential issue | 🟡 Minor

Hydration flicker / mismatch after removing useHydrated guard.

next-themes cannot read localStorage during SSR, so theme is undefined on the server and the ?? 'system' fallback always renders SunMoonIcon + the "system" label at SSR. On the client, after hydration theme resolves to the user's persisted value (e.g. 'light'/'dark'), causing an icon/label swap on first paint and a React hydration mismatch on this subtree. The suppressHydrationWarning on the root <html> element does not suppress warnings in nested components that render differently between server and client.

This affects:

  • src/components/ui/theme-switcher.tsx (lines 38–48)
  • src/layout/manager/nav-user.tsx (lines 114–132)

Consider wrapping these components with suppressHydrationWarning, or use next-themes' resolvedTheme with an explicit placeholder instead of reading theme directly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/theme-switcher.tsx` around lines 38 - 48, The hydration
mismatch comes from reading theme directly (variable theme, themes array, match,
SunMoonIcon/SunIcon/MoonIcon, t) during SSR; switch to using next-themes'
resolvedTheme (from useTheme) or guard client-only rendering: in
src/components/ui/theme-switcher.tsx and src/layout/manager/nav-user.tsx,
replace reads of theme ?? 'system' with resolvedTheme (or check mounted/window)
and if resolvedTheme is undefined render a stable SSR placeholder (e.g., the
neutral system icon/label) or wrap the icon/label subtree in an element with
suppressHydrationWarning to avoid hydration errors; ensure the match() calls use
resolvedTheme (or the mounted check) so server and client initial output match.
src/features/auth/utils.ts (1)

14-17: ⚠️ Potential issue | 🟠 Major

Redirect as soon as prefetched session data is available.

The new useSession() can expose root-prefetched session.data while the client session request is still pending. Keeping session.isPending in this guard delays redirects until the client refetch completes, which undermines the spinner-avoidance flow.

Proposed fix
-      if (session.isPending || !session.data) {
+      if (!session.data) {
         return;
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/auth/utils.ts` around lines 14 - 17, The effect's exec function
delays redirects by checking session.isPending even when root-prefetched
session.data is already available; update the guard inside useEffect/exec to
only gate on session.data (e.g., replace "if (session.isPending ||
!session.data) return" with a check that returns when !session.data) so
redirects run immediately when session.data exists from server prefetch while
avoiding waiting for client refetch.
src/features/auth/guard-public-only.tsx (1)

10-20: ⚠️ Potential issue | 🟠 Major

Avoid showing the public page or full spinner when a prefetched session exists.

useSession() can provide session.data from the root prefetch while the client refetch is still pending. This branch still shows <Spinner full /> on isPending, and when not pending it renders children even for authenticated users until the redirect effect runs.

Proposed fix
 export const GuardPublicOnly = ({ children }: { children?: ReactNode }) => {
   const session = useSession();
   useRedirectAfterLogin();
+  const isLoadingWithoutSession = session.isPending && !session.data;
+  const shouldShowChildren = !isLoadingWithoutSession && !session.data;
 
   if (session.error && session.error.status > 0) {
     return <PageError type="unknown-auth-error" />;
   }
 
   return (
     <>
-      {session.isPending && <Spinner full />}
-      <Activity mode={session.isPending ? 'hidden' : 'visible'}>
+      {isLoadingWithoutSession && <Spinner full />}
+      <Activity mode={shouldShowChildren ? 'visible' : 'hidden'}>
         {children}
       </Activity>
     </>
   );
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/auth/guard-public-only.tsx` around lines 10 - 20, The guard
currently shows the full-page spinner or public children while a prefetched
session exists; update the logic in the component that calls useSession() and
useRedirectAfterLogin() so that if session.data is present (i.e., a prefetched
authenticated session) you do not render the public children or the full
<Spinner full />: when session.data && session.isPending render a minimal/hidden
loading state (avoid the full spinner), and when session.data &&
!session.isPending return nothing (or a placeholder) so authenticated users are
not shown the public page while the redirect effect in useRedirectAfterLogin
runs; keep the existing session.error check intact.
🧹 Nitpick comments (2)
src/routes/__root.tsx (1)

5-11: Merge duplicate @tanstack/react-router imports.

As flagged by SonarCloud, getRouteApi can be added to the existing import block instead of a second import statement.

♻️ Proposed refactor
 import {
   createRootRouteWithContext,
+  getRouteApi,
   HeadContent,
   Outlet,
   Scripts,
 } from '@tanstack/react-router';
-import { getRouteApi } from '@tanstack/react-router';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/__root.tsx` around lines 5 - 11, Merge the duplicate imports from
`@tanstack/react-router` by adding getRouteApi to the existing import declaration
that already imports createRootRouteWithContext, HeadContent, Outlet, and
Scripts; update the import statement in the module containing
createRootRouteWithContext/HeadContent/Outlet/Scripts to a single import that
includes getRouteApi and remove the separate getRouteApi import.
src/routes/index.tsx (1)

7-10: Reuse the root-route authSession from context instead of refetching.

The root beforeLoad already fetches the session on each request and exposes it through route context. Calling getAuthSession() here duplicates the server round-trip unnecessarily. Consume the parent context instead:

♻️ Proposed refactor
-import { getAuthSession } from '@/features/auth/session.server';
-
 export const Route = createFileRoute('/')({
   component: RouteComponent,
-  beforeLoad: async () => {
-    const authSession = await getAuthSession();
-    throw redirect({ to: authSession?.session ? '/app' : '/login' });
+  beforeLoad: ({ context }) => {
+    throw redirect({ to: context.authSession?.session ? '/app' : '/login' });
   },
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/index.tsx` around lines 7 - 10, The beforeLoad handler currently
calls getAuthSession() and causes an extra server round-trip; instead read the
existing authSession exposed by the root route context and use that value to
decide the redirect. Replace the getAuthSession() call in beforeLoad with the
parent/root route context lookup (the authSession previously set on the root
loader) and then call redirect({ to: authSession?.session ? '/app' : '/login' })
using that context value (referencing beforeLoad, getAuthSession, authSession,
and redirect to locate and update the code).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/features/auth/use-session.ts`:
- Around line 17-23: The guard currently uses authSession?.session !== undefined
which is always true when authSession exists (because getAuthSession returns
session ?? null), causing null server-prefetched sessions to be treated as valid
fallbacks; update the conditional in use-session.ts where session.isPending is
checked (around the block that returns {...session, data: authSession.session
satisfies typeof session.data, isPending: false}) to use a truthy check (e.g.,
authSession?.session) instead of !== undefined so the fallback only applies for
real prefetched session data and not for explicit nulls or client-only
navigations.

---

Outside diff comments:
In `@src/components/ui/theme-switcher.tsx`:
- Around line 38-48: The hydration mismatch comes from reading theme directly
(variable theme, themes array, match, SunMoonIcon/SunIcon/MoonIcon, t) during
SSR; switch to using next-themes' resolvedTheme (from useTheme) or guard
client-only rendering: in src/components/ui/theme-switcher.tsx and
src/layout/manager/nav-user.tsx, replace reads of theme ?? 'system' with
resolvedTheme (or check mounted/window) and if resolvedTheme is undefined render
a stable SSR placeholder (e.g., the neutral system icon/label) or wrap the
icon/label subtree in an element with suppressHydrationWarning to avoid
hydration errors; ensure the match() calls use resolvedTheme (or the mounted
check) so server and client initial output match.

In `@src/features/auth/guard-public-only.tsx`:
- Around line 10-20: The guard currently shows the full-page spinner or public
children while a prefetched session exists; update the logic in the component
that calls useSession() and useRedirectAfterLogin() so that if session.data is
present (i.e., a prefetched authenticated session) you do not render the public
children or the full <Spinner full />: when session.data && session.isPending
render a minimal/hidden loading state (avoid the full spinner), and when
session.data && !session.isPending return nothing (or a placeholder) so
authenticated users are not shown the public page while the redirect effect in
useRedirectAfterLogin runs; keep the existing session.error check intact.

In `@src/features/auth/utils.ts`:
- Around line 14-17: The effect's exec function delays redirects by checking
session.isPending even when root-prefetched session.data is already available;
update the guard inside useEffect/exec to only gate on session.data (e.g.,
replace "if (session.isPending || !session.data) return" with a check that
returns when !session.data) so redirects run immediately when session.data
exists from server prefetch while avoiding waiting for client refetch.

---

Nitpick comments:
In `@src/routes/__root.tsx`:
- Around line 5-11: Merge the duplicate imports from `@tanstack/react-router` by
adding getRouteApi to the existing import declaration that already imports
createRootRouteWithContext, HeadContent, Outlet, and Scripts; update the import
statement in the module containing
createRootRouteWithContext/HeadContent/Outlet/Scripts to a single import that
includes getRouteApi and remove the separate getRouteApi import.

In `@src/routes/index.tsx`:
- Around line 7-10: The beforeLoad handler currently calls getAuthSession() and
causes an extra server round-trip; instead read the existing authSession exposed
by the root route context and use that value to decide the redirect. Replace the
getAuthSession() call in beforeLoad with the parent/root route context lookup
(the authSession previously set on the root loader) and then call redirect({ to:
authSession?.session ? '/app' : '/login' }) using that context value
(referencing beforeLoad, getAuthSession, authSession, and redirect to locate and
update the code).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2af05936-1682-4f7c-b69e-48a1c3a2a1e3

📥 Commits

Reviewing files that changed from the base of the PR and between fc84bff and 7326a18.

📒 Files selected for processing (19)
  • src/components/ui/theme-switcher.tsx
  • src/features/account/change-name-drawer.tsx
  • src/features/account/user-card.tsx
  • src/features/auth/guard-authenticated.tsx
  • src/features/auth/guard-public-only.tsx
  • src/features/auth/page-login-verify.tsx
  • src/features/auth/page-logout.tsx
  • src/features/auth/page-onboarding.tsx
  • src/features/auth/session.server.ts
  • src/features/auth/use-session.ts
  • src/features/auth/utils.ts
  • src/features/auth/with-permissions.tsx
  • src/features/demo/demo-app-switch.tsx
  • src/features/user/manager/form-user.tsx
  • src/features/user/manager/page-user-update.tsx
  • src/features/user/manager/page-user.tsx
  • src/layout/manager/nav-user.tsx
  • src/routes/__root.tsx
  • src/routes/index.tsx

Comment thread src/features/auth/use-session.ts Outdated
Copy link
Copy Markdown
Member

@ivan-dalmet ivan-dalmet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy and e2e need to be fixed 😉

Comment thread src/routes/index.tsx Outdated
throw redirect({ to: '/login' });
beforeLoad: async () => {
const authSession = await getAuthSession();
throw redirect({ to: authSession?.session ? '/app' : '/login' });
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be /app or /manager depending of the role to match previous behaviour?

Comment thread src/features/auth/use-session.ts Outdated
const session = authClient.useSession();
const { authSession } = RootRouteApi.useRouteContext();

if (session.isPending && authSession?.session !== undefined) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check code rabbit comment

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/routes/__root.tsx (1)

37-41: Naming consistency nit.

The existing helper above is initSsrApp (verb + Ssr + scope), but the new one is initAuthSsr (verb + scope + Ssr). Consider initSsrAuth to keep a consistent pattern, or rename both to a single convention.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/__root.tsx` around lines 37 - 41, Rename the new server-side
helper initAuthSsr to follow the existing verb + Ssr + scope pattern (e.g.,
rename initAuthSsr to initSsrAuth) so it matches initSsrApp; update the
declaration created from createServerFn({ method: 'GET' }).handler(async () => {
return { authSession: await getAuthSession(), }; }); and change all
references/usages of initAuthSsr to initSsrAuth across the codebase to keep
naming consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/routes/__root.tsx`:
- Around line 46-52: beforeLoad currently awaits initAuthSsr() directly which
lets failures from auth.api.getSession() bubble and break the app shell; wrap
the await initAuthSsr() call inside a try/catch in the beforeLoad function (only
on SSR path) and on any exception return { authSession: null } (and optionally
log the error), so unauthenticated/public routes still render and
authClient.useSession() can re-fetch on the client; reference: beforeLoad and
initAuthSsr / AuthSession.

---

Nitpick comments:
In `@src/routes/__root.tsx`:
- Around line 37-41: Rename the new server-side helper initAuthSsr to follow the
existing verb + Ssr + scope pattern (e.g., rename initAuthSsr to initSsrAuth) so
it matches initSsrApp; update the declaration created from createServerFn({
method: 'GET' }).handler(async () => { return { authSession: await
getAuthSession(), }; }); and change all references/usages of initAuthSsr to
initSsrAuth across the codebase to keep naming consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7cb9649e-5d86-40ad-9932-edcb4525965f

📥 Commits

Reviewing files that changed from the base of the PR and between 3a38295 and 081c092.

📒 Files selected for processing (1)
  • src/routes/__root.tsx

Comment thread src/routes/__root.tsx
Comment on lines +46 to +52
beforeLoad: async (): Promise<{ authSession: AuthSession | null }> => {
if (!import.meta.env.SSR) {
return { authSession: null };
}
const { authSession } = await initAuthSsr();
return { authSession };
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Consider guarding beforeLoad against getAuthSession failures.

If auth.api.getSession() throws (DB outage, transient network blip, malformed cookie, etc.), the rejection propagates out of the root beforeLoad, which will fail the entire app shell — including public/unauthenticated routes that don't need a session at all. Since the whole point of this change is to improve perceived load behavior, swallowing the failure to { authSession: null } keeps the app rendering and lets authClient.useSession() re-fetch on the client.

🛡️ Suggested resilience tweak
   beforeLoad: async (): Promise<{ authSession: AuthSession | null }> => {
     if (!import.meta.env.SSR) {
       return { authSession: null };
     }
-    const { authSession } = await initAuthSsr();
-    return { authSession };
+    try {
+      const { authSession } = await initAuthSsr();
+      return { authSession };
+    } catch {
+      // Fall back to client-side session fetch if SSR prefetch fails.
+      return { authSession: null };
+    }
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
beforeLoad: async (): Promise<{ authSession: AuthSession | null }> => {
if (!import.meta.env.SSR) {
return { authSession: null };
}
const { authSession } = await initAuthSsr();
return { authSession };
},
beforeLoad: async (): Promise<{ authSession: AuthSession | null }> => {
if (!import.meta.env.SSR) {
return { authSession: null };
}
try {
const { authSession } = await initAuthSsr();
return { authSession };
} catch {
// Fall back to client-side session fetch if SSR prefetch fails.
return { authSession: null };
}
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/__root.tsx` around lines 46 - 52, beforeLoad currently awaits
initAuthSsr() directly which lets failures from auth.api.getSession() bubble and
break the app shell; wrap the await initAuthSsr() call inside a try/catch in the
beforeLoad function (only on SSR path) and on any exception return {
authSession: null } (and optionally log the error), so unauthenticated/public
routes still render and authClient.useSession() can re-fetch on the client;
reference: beforeLoad and initAuthSsr / AuthSession.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/features/auth/session.ts (1)

6-20: Consider a name that reflects the actual usage.

initAuthSsr reads as a one-shot SSR initializer, but this is a regular TanStack server function that is also invoked from client-side beforeLoad (e.g. src/routes/index.tsx on client navigation, where it becomes an HTTP roundtrip). A name like getAuthSession / fetchAuthSession / getAuthSessionFn would more accurately convey that it is a server function fetching the current session on demand, not just an SSR bootstrap step. The exported AuthSession type would then naturally read as the result of that fetch.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/auth/session.ts` around lines 6 - 20, The name initAuthSsr is
misleading—rename the server function to something like getAuthSession (or
fetchAuthSession/getAuthSessionFn) and update the exported type name accordingly
(AuthSession should remain the result type of the renamed function); locate the
function created via createServerFn and the AuthSession type definition, change
the identifier initAuthSsr to getAuthSession, and update all call sites/imports
(e.g., client beforeLoad usage and any references to initAuthSsr) so they import
and call the new name; ensure the implementation still calls auth.api.getSession
with getRequestHeaders() and that any tests or types referencing
Awaited<ReturnType<typeof initAuthSsr>> are updated to Awaited<ReturnType<typeof
getAuthSession>>.
src/routes/index.tsx (1)

9-28: Reuse the root route's prefetched authSession from context to avoid a duplicate session fetch.

The root route's beforeLoad already awaits initAuthSsr() and exposes authSession on the route context. On initial SSR load, this beforeLoad then calls initAuthSsr() a second time, producing a redundant auth.api.getSession lookup (a duplicate DB roundtrip) for every visit to /. On client navigation the root returns { authSession: null }, so a fresh fetch is still required there.

You can read from the parent context first and only invoke the server function when it is unavailable:

♻️ Proposed refactor
-  beforeLoad: async () => {
-    const { authSession } = await initAuthSsr();
+  beforeLoad: async ({ context }) => {
+    const authSession =
+      context.authSession ?? (await initAuthSsr()).authSession;

     if (!authSession?.session) {
       throw redirect({ to: '/login' });
     }

     if (
       authClient.admin.checkRolePermission({
-        role: authSession.user.role as Role,
+        role: authSession.user.role as Role,
         permissions: {
           apps: ['manager'],
         },
       })
     ) {
       throw redirect({ to: '/manager' });
     }

     throw redirect({ to: '/app' });
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/index.tsx` around lines 9 - 28, The beforeLoad currently always
calls initAuthSsr() causing a duplicate session fetch; change beforeLoad to
first read the parent route context for authSession and only call initAuthSsr()
when that context value is missing (e.g., on client navigation). Update the
logic inside beforeLoad (referencing beforeLoad, authSession, and initAuthSsr)
so you use the parent-provided authSession if present, then perform the same
role check via authClient.admin.checkRolePermission and redirect accordingly;
only fall back to calling initAuthSsr() when parent context has no authSession.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/features/auth/session.ts`:
- Around line 6-20: The name initAuthSsr is misleading—rename the server
function to something like getAuthSession (or fetchAuthSession/getAuthSessionFn)
and update the exported type name accordingly (AuthSession should remain the
result type of the renamed function); locate the function created via
createServerFn and the AuthSession type definition, change the identifier
initAuthSsr to getAuthSession, and update all call sites/imports (e.g., client
beforeLoad usage and any references to initAuthSsr) so they import and call the
new name; ensure the implementation still calls auth.api.getSession with
getRequestHeaders() and that any tests or types referencing
Awaited<ReturnType<typeof initAuthSsr>> are updated to Awaited<ReturnType<typeof
getAuthSession>>.

In `@src/routes/index.tsx`:
- Around line 9-28: The beforeLoad currently always calls initAuthSsr() causing
a duplicate session fetch; change beforeLoad to first read the parent route
context for authSession and only call initAuthSsr() when that context value is
missing (e.g., on client navigation). Update the logic inside beforeLoad
(referencing beforeLoad, authSession, and initAuthSsr) so you use the
parent-provided authSession if present, then perform the same role check via
authClient.admin.checkRolePermission and redirect accordingly; only fall back to
calling initAuthSsr() when parent context has no authSession.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5ecd6803-4a05-4b26-8663-25e16401cc81

📥 Commits

Reviewing files that changed from the base of the PR and between 081c092 and 5cc2d94.

📒 Files selected for processing (4)
  • src/features/auth/session.ts
  • src/features/auth/use-session.ts
  • src/routes/__root.tsx
  • src/routes/index.tsx
✅ Files skipped from review due to trivial changes (1)
  • src/routes/__root.tsx

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/features/user/manager/page-user-new.tsx (1)

41-49: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Successful submit navigation can be blocked by dirty-form guard

At Line 48, router.navigate(...) can be intercepted because PreventNavigation (Line 69) still blocks when form.formState.isDirty is true. After create success, this may show a blocker prompt instead of redirecting, which regresses the post-submit flow.

Suggested fix
      onSuccess: async () => {
        // Invalidate Users list
        await queryClient.invalidateQueries({
          queryKey: orpc.user.getAll.key(),
          type: 'all',
        });

+       form.reset();
        await router.navigate({ to: '/manager/users' });
      },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/user/manager/page-user-new.tsx` around lines 41 - 49, The
post-submit redirect can be blocked by the PreventNavigation guard because
form.formState.isDirty remains true; in the onSuccess handler (function
onSuccess where you call queryClient.invalidateQueries and router.navigate)
reset the form's dirty state before navigating — e.g. call form.reset(...) or
otherwise clear form.formState.isDirty (or flip a guard-bypass flag consumed by
PreventNavigation) right after invalidating queries and before calling
router.navigate({ to: '/manager/users' }) so the navigation isn't intercepted.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/features/user/manager/page-user-new.tsx`:
- Around line 41-49: The post-submit redirect can be blocked by the
PreventNavigation guard because form.formState.isDirty remains true; in the
onSuccess handler (function onSuccess where you call
queryClient.invalidateQueries and router.navigate) reset the form's dirty state
before navigating — e.g. call form.reset(...) or otherwise clear
form.formState.isDirty (or flip a guard-bypass flag consumed by
PreventNavigation) right after invalidating queries and before calling
router.navigate({ to: '/manager/users' }) so the navigation isn't intercepted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fca642b4-df59-40a0-849d-dd1bfd284d60

📥 Commits

Reviewing files that changed from the base of the PR and between 5cc2d94 and 0997b30.

📒 Files selected for processing (2)
  • src/features/auth/guard-authenticated.tsx
  • src/features/user/manager/page-user-new.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/features/auth/guard-authenticated.tsx

@HugoPerard HugoPerard force-pushed the improve-session-loader branch from 0997b30 to e6db0cd Compare May 7, 2026 09:23
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 7, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants