diff --git a/.changeset/salty-windows-crash.md b/.changeset/salty-windows-crash.md new file mode 100644 index 00000000..62980c1c --- /dev/null +++ b/.changeset/salty-windows-crash.md @@ -0,0 +1,5 @@ +--- +"create-better-t-stack": patch +--- + +TanStack Router checks for auth in the beforeLoad function of a Route diff --git a/apps/cli/templates/auth/web/react/tanstack-router/src/components/sign-in-form.tsx b/apps/cli/templates/auth/web/react/tanstack-router/src/components/sign-in-form.tsx index 65bfab25..6d3e47df 100644 --- a/apps/cli/templates/auth/web/react/tanstack-router/src/components/sign-in-form.tsx +++ b/apps/cli/templates/auth/web/react/tanstack-router/src/components/sign-in-form.tsx @@ -1,6 +1,6 @@ import { authClient } from "@/lib/auth-client"; import { useForm } from "@tanstack/react-form"; -import { useNavigate } from "@tanstack/react-router"; +import { useNavigate, useSearch } from "@tanstack/react-router"; import { toast } from "sonner"; import z from "zod"; import Loader from "./loader"; @@ -13,6 +13,7 @@ export default function SignInForm({ }: { onSwitchToSignUp: () => void; }) { + const search = useSearch({ from: "/login" }); const navigate = useNavigate({ from: "/", }); @@ -32,7 +33,7 @@ export default function SignInForm({ { onSuccess: () => { navigate({ - to: "/dashboard", + to: search.redirect, }); toast.success("Sign in successful"); }, diff --git a/apps/cli/templates/auth/web/react/tanstack-router/src/components/sign-up-form.tsx b/apps/cli/templates/auth/web/react/tanstack-router/src/components/sign-up-form.tsx index 3bd480f0..826fbeea 100644 --- a/apps/cli/templates/auth/web/react/tanstack-router/src/components/sign-up-form.tsx +++ b/apps/cli/templates/auth/web/react/tanstack-router/src/components/sign-up-form.tsx @@ -1,6 +1,6 @@ import { authClient } from "@/lib/auth-client"; import { useForm } from "@tanstack/react-form"; -import { useNavigate } from "@tanstack/react-router"; +import { useNavigate, useSearch } from "@tanstack/react-router"; import { toast } from "sonner"; import z from "zod"; import Loader from "./loader"; @@ -13,6 +13,7 @@ export default function SignUpForm({ }: { onSwitchToSignIn: () => void; }) { + const search = useSearch({ from: "/login" }); const navigate = useNavigate({ from: "/", }); @@ -34,7 +35,7 @@ export default function SignUpForm({ { onSuccess: () => { navigate({ - to: "/dashboard", + to: search.redirect, }); toast.success("Sign up successful"); }, diff --git a/apps/cli/templates/auth/web/react/tanstack-router/src/routes/_auth.tsx b/apps/cli/templates/auth/web/react/tanstack-router/src/routes/_auth.tsx new file mode 100644 index 00000000..83cc6e20 --- /dev/null +++ b/apps/cli/templates/auth/web/react/tanstack-router/src/routes/_auth.tsx @@ -0,0 +1,15 @@ +import { createFileRoute, Outlet, redirect } from "@tanstack/react-router"; + +export const Route = createFileRoute("/_auth")({ + beforeLoad: async ({ context }) => { + if (!context.user) { + throw redirect({ + to: "/login", + search: { + redirect: `${location.pathname}${location.search}${location.hash}`, + }, + }); + } + }, + component: () => , +}); diff --git a/apps/cli/templates/auth/web/react/tanstack-router/src/routes/dashboard.tsx.hbs b/apps/cli/templates/auth/web/react/tanstack-router/src/routes/_auth/dashboard.tsx.hbs similarity index 78% rename from apps/cli/templates/auth/web/react/tanstack-router/src/routes/dashboard.tsx.hbs rename to apps/cli/templates/auth/web/react/tanstack-router/src/routes/_auth/dashboard.tsx.hbs index 3ae44b6f..97e2dc27 100644 --- a/apps/cli/templates/auth/web/react/tanstack-router/src/routes/dashboard.tsx.hbs +++ b/apps/cli/templates/auth/web/react/tanstack-router/src/routes/_auth/dashboard.tsx.hbs @@ -9,32 +9,22 @@ import { trpc } from "@/utils/trpc"; import { useQuery } from "@tanstack/react-query"; {{/if}} import { createFileRoute } from "@tanstack/react-router"; -import { useEffect } from "react"; -export const Route = createFileRoute("/dashboard")({ +export const Route = createFileRoute("/_auth/dashboard")({ component: RouteComponent, }); function RouteComponent() { const { data: session, isPending } = authClient.useSession(); - - const navigate = Route.useNavigate(); - {{#if (eq api "orpc")}} + const privateData = useQuery(orpc.privateData.queryOptions()); {{/if}} {{#if (eq api "trpc")}} + const privateData = useQuery(trpc.privateData.queryOptions()); {{/if}} - useEffect(() => { - if (!session && !isPending) { - navigate({ - to: "/login", - }); - } - }, [session, isPending]); - if (isPending) { return
Loading...
; } diff --git a/apps/cli/templates/auth/web/react/tanstack-router/src/routes/login.tsx b/apps/cli/templates/auth/web/react/tanstack-router/src/routes/login.tsx index 81dc6a0a..cc602ee6 100644 --- a/apps/cli/templates/auth/web/react/tanstack-router/src/routes/login.tsx +++ b/apps/cli/templates/auth/web/react/tanstack-router/src/routes/login.tsx @@ -1,9 +1,20 @@ import SignInForm from "@/components/sign-in-form"; import SignUpForm from "@/components/sign-up-form"; -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, redirect } from "@tanstack/react-router"; import { useState } from "react"; +import { z } from "zod"; + +const fallbackRedirect = "/dashboard"; export const Route = createFileRoute("/login")({ + validateSearch: z.object({ + redirect: z.string().default(fallbackRedirect), + }), + beforeLoad: async ({ context, search }) => { + if (context.user) { + throw redirect({ to: search.redirect }); + } + }, component: RouteComponent, }); diff --git a/apps/cli/templates/frontend/react/tanstack-router/src/main.tsx.hbs b/apps/cli/templates/frontend/react/tanstack-router/src/main.tsx.hbs index 2596dae6..f4cff79b 100644 --- a/apps/cli/templates/frontend/react/tanstack-router/src/main.tsx.hbs +++ b/apps/cli/templates/frontend/react/tanstack-router/src/main.tsx.hbs @@ -21,7 +21,7 @@ const router = createRouter({ defaultPreload: "intent", defaultPendingComponent: () => , {{#if (eq api "orpc")}} - context: { orpc, queryClient }, + context: { orpc, queryClient{{#if auth}}, user: null{{/if}} }, Wrap: function WrapComponent({ children }: { children: React.ReactNode }) { return ( @@ -30,7 +30,7 @@ const router = createRouter({ ); }, {{else if (eq api "trpc")}} - context: { trpc, queryClient }, + context: { trpc, queryClient{{#if auth}}, user: null{{/if}} }, Wrap: function WrapComponent({ children }: { children: React.ReactNode }) { return ( @@ -44,7 +44,9 @@ const router = createRouter({ return {children}; }, {{else}} - context: {}, + context: { + {{#if auth}}user: null{{/if}} + }, {{/if}} }); diff --git a/apps/cli/templates/frontend/react/tanstack-router/src/routes/__root.tsx.hbs b/apps/cli/templates/frontend/react/tanstack-router/src/routes/__root.tsx.hbs index 03c3d2f7..752415d5 100644 --- a/apps/cli/templates/frontend/react/tanstack-router/src/routes/__root.tsx.hbs +++ b/apps/cli/templates/frontend/react/tanstack-router/src/routes/__root.tsx.hbs @@ -17,6 +17,10 @@ import type { trpc } from "@/utils/trpc"; import type { QueryClient } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; {{/if}} +{{#if auth}} +import { authClient } from "@/lib/auth-client"; +import type { User } from "better-auth"; +{{/if}} import { HeadContent, Outlet, @@ -30,17 +34,38 @@ import "../index.css"; export interface RouterAppContext { orpc: typeof orpc; queryClient: QueryClient; + {{#if auth}} + user: User | null; + {{/if}} } {{else if (eq api "trpc")}} export interface RouterAppContext { trpc: typeof trpc; queryClient: QueryClient; + {{#if auth}} + user: User | null; + {{/if}} } {{else}} -export interface RouterAppContext {} +export interface RouterAppContext { + {{#if auth}} + user: User | null; + {{/if}} +} {{/if}} export const Route = createRootRouteWithContext()({ + {{#if auth}} + beforeLoad: async () => { + try { + const session = await authClient.getSession(); + return { user: session.data?.user ?? null }; + } catch (error) { + console.error('Failed to fetch user session:', error); + return { user: null }; + } + }, + {{/if}} component: RootComponent, head: () => ({ meta: [ @@ -65,8 +90,8 @@ function RootComponent() { const isFetching = useRouterState({ select: (s) => s.isLoading, }); - {{#if (eq api "orpc")}} + const [client] = useState>(() => createORPCClient(link)); const [orpcUtils] = useState(() => createTanstackQueryUtils(client)); {{/if}}