fix(routing): redirect anonymous users from protected routes + devtools rename
Bug 1: hard-loading / while unauthenticated stayed stuck on "Loading..." forever. Cause: the _authed layout short-circuits to the spinner when status is anything other than 'authenticated', which means child routes never mount. The redirect-on-anonymous useEffect lived only in the home page (_authed/index.tsx), so it never fired — the layout's spinner was the last thing rendered. Fix: add a useEffect to the _authed layout component itself that navigates to /login on the 'anonymous' transition. The layout's gate is now: beforeLoad redirects on cold-known-anonymous, useEffect redirects on post-mount transitions to anonymous (e.g. boot probe resolves to anonymous after the route already rendered). Bug 2: console warning that @tanstack/router-devtools moved to @tanstack/react-router-devtools. Same package, renamed. Fix: pnpm remove @tanstack/router-devtools && pnpm add -D @tanstack/react-router-devtools; updated the lazy import in __root.tsx to point at the new package name. Plus: TRM_Design_System-handoff/ added to .prettierignore — those files are immutable source material from claude.ai/design and shouldn't be reformatted.
This commit is contained in:
@@ -8,7 +8,7 @@ import { queryClient } from '@/lib/query-client';
|
||||
// `import.meta.env.DEV`.
|
||||
const TanStackRouterDevtools = import.meta.env.DEV
|
||||
? lazy(() =>
|
||||
import('@tanstack/router-devtools').then((mod) => ({
|
||||
import('@tanstack/react-router-devtools').then((mod) => ({
|
||||
default: mod.TanStackRouterDevtools,
|
||||
})),
|
||||
)
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
import { Outlet, createFileRoute, redirect } from '@tanstack/react-router';
|
||||
import { useEffect } from 'react';
|
||||
import { Outlet, createFileRoute, redirect, useNavigate } from '@tanstack/react-router';
|
||||
import { useAuthStore } from '@/auth';
|
||||
|
||||
/**
|
||||
* Pathless layout for all protected routes.
|
||||
*
|
||||
* `beforeLoad` is the canonical TanStack Router gate — runs before any
|
||||
* loader/component on this branch. If the user is anonymous when the
|
||||
* router resolves the route, redirect to `/login` carrying the intended
|
||||
* destination as `?redirect=`.
|
||||
* Two complementary gates:
|
||||
*
|
||||
* If status is `'unknown'` or `'authenticating'` (boot probe still in
|
||||
* flight), let the route render — the layout component below shows a
|
||||
* loading placeholder until the probe resolves. Without that, the user
|
||||
* gets an unwanted login flash on every hard reload.
|
||||
* 1. `beforeLoad` — runs on navigation, before the route mounts. If the
|
||||
* user is already known anonymous, redirect to `/login` carrying the
|
||||
* intended destination as `?redirect=`. This is the fast path on a
|
||||
* cold visit when the boot probe has already resolved.
|
||||
*
|
||||
* 2. The layout component's effect — runs on every status transition
|
||||
* *after* mount. Catches the case where `beforeLoad` saw `'unknown'`
|
||||
* or `'authenticating'` (boot probe still in flight) and let the
|
||||
* route render; once the probe resolves to `'anonymous'`, this effect
|
||||
* navigates to `/login`. Without this fallback the user is stuck on a
|
||||
* "Loading…" screen forever after a hard reload.
|
||||
*/
|
||||
export const Route = createFileRoute('/_authed')({
|
||||
beforeLoad: ({ location }) => {
|
||||
@@ -29,6 +34,17 @@ export const Route = createFileRoute('/_authed')({
|
||||
|
||||
function AuthedLayout() {
|
||||
const status = useAuthStore((s) => s.status);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'anonymous') {
|
||||
void navigate({
|
||||
to: '/login',
|
||||
search: { redirect: window.location.pathname + window.location.search },
|
||||
replace: true,
|
||||
});
|
||||
}
|
||||
}, [status, navigate]);
|
||||
|
||||
if (status !== 'authenticated') {
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user