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}}