step by step guide to setup the authentication in vite tanstack router app with supabase
First Create the tanstack router boilerplate using this command
pnpm dlx create-tsrouter-app@latest app-name --template file-router
then install supabase js client using
pnpm add @supabase/supabase-js
After intstalling the @supabase/supabase-js
module. create the .env
file in the root of the project and add your supabase credentials in it like this.
VITE_SUPABASE_URL=<your supabase project url>
VITE_SUPABASE_ANON_KEY=<your supabase anon key>
Then create the supabase.ts file and create supabase client in it
import { createClient } from “@supabase/supabase-js”;
export const supabase = createClient(import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY);
After creating the supabase client go to __root.ts
file and replace createRootRoute
function with createRootRouteWithContext
and add the auth types in it like this.
import type { User } from “@supabase/supabase-js”;
import { Outlet, createRootRouteWithContext } from “@tanstack/react-router”;
import { TanStackRouterDevtools } from “@tanstack/react-router-devtools”;
export const Route = createRootRouteWithContext<{auth: User | null}>()({
component: () => (
<><Outlet />
<TanStackRouterDevtools />
</>)});
then go to main.ts
file and replace router instance context with this
// Create a new router instance
const router = createRouter({
routeTree,
context: { auth: null },
defaultPreload: "intent",
scrollRestoration: true,
defaultStructuralSharing: true,
defaultPreloadStaleTime: 0,
});
After that create the App()
function in the main.ts
file
function App() {
const session = useAuth();
return <RouterProvider router={router} context={{ auth: session }} />;
}
here’s the useAuth
hook code.
import { supabase } from "@/supabase";
import type { User } from "@supabase/supabase-js";
import { useEffect, useState } from "react";
function getSupabaseAuthTokenKey(url: string): string {
try {
const hostname = new URL(url).hostname;
const projectId = hostname.split(".")[0];
return `sb-${projectId}-auth-token`; // supabase save session details in localStorage with this type of key format.
} catch (error) {
throw new Error("Invalid Supabase URL");
}
}
export function useAuth() {
const [session, setSession] = useState<{ user: User | null }>(
JSON.parse(
localStorage.getItem(
getSupabaseAuthTokenKey(import.meta.env.VITE_SUPABASE_URL)
) || "{}"
) || null
);
useEffect(() => {
const initialize = async () => {
const { data } = await supabase.auth.getUser();
setSession(data);
};
initialize();
}, []);
return session.user;
}
then replace the <RouterProvider router-{router}/>
with the <App/>
in root.render()
Method like this.
// Render the app
const rootElement = document.getElementById("app");
if (rootElement && !rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement);
root.render(
<StrictMode>
<App />
</StrictMode>
);
}
then create a new unauthenticated route file for handling login under the route folder like this routes/(auth)/sign-in.tsx
then paste this code
import { supabase } from "@/supabase";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
export const Route = createFileRoute("/(auth)/sign-in")({
component: SignIn,
});
function SignIn() {
const navigate = useNavigate({ from: "/sign-in" });
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); // Prevent the default form submission
const formData = new FormData(event.currentTarget); // Get form data
const data = {
email: formData.get("email"),
password: formData.get("password"),
};
// Example: validation (optional)
if (!data.email || !data.password) {
alert("Please fill in all fields");
return;
}
const { data: sessionData } = await supabase.auth.signInWithPassword({
email: data.email as string,
password: data.password as string,
});
if (sessionData.session?.user.id) {
navigate({ to: "/" });
}
};
return (
<form onSubmit={handleSubmit} className="grid gap-3">
<label htmlFor="email">Email</label>
<input
name="email"
type="email"
placeholder="name@example.com"
required
/>
<label htmlFor="password">Password</label>
<input name="password" type="password" placeholder="********" required />
<button className="mt-2" type="submit">
Login
</button>
</form>
);
}
This is a simple unstyled login form to handle login functionality.
Until now, we have done these steps:
- Created the boilerplate code with Tanstack router + vite.
- Added
.env
variables for the supabase client. - Initialized Supabase client.
- Added the Auth context in the router.
- Created the
useAuth()
hook. - Created the
App()
function inmain.ts
and integrated withinroot.render()
method. - Created the
routes/(auth)/sign-in.tsx
route and implemented the login functionality
Now we, need to create the authenticated routes to check if the authentication really working or not.
We need these steps to done:
- Create the
_authenticated/route.tsx
layout route & implement authentication - Move
routes/index.tsx
toroutes/_authenticated/index.tsx
So then, under the routes
folder create the _authenticated
folder and then create the route.tsx
file. Now the folder structure of routes
folder should look like this.
├── App.css
├── hook
│ └── use-auth.tsx
├── logo.svg
├── main.tsx
├── reportWebVitals.ts
├── routes # This is the route folder in which we are working
│ ├── (auth)
│ │ └── sign-in.tsx
│ ├── \_authenticated
│ │ ├── index.tsx # "/" protected route
│ │ └── route.tsx # The authenticated layout file
│ └── \_\_root.tsx
├── routeTree.gen.ts
├── styles.css
└── supabase.ts
routes/_authenticated/routes.tsx
file
import { supabase } from "@/supabase";
import { createFileRoute, Outlet, redirect } from "@tanstack/react-router";
export const Route = createFileRoute("/_authenticated")({
async beforeLoad({ context: { auth } }) {
if (!auth?.id) {
const { data } = await supabase.auth.getUser();
if (!data.user?.id) throw redirect({ to: "/sign-in" });
return { auth: data.user };
}
},
component: RouteComponent,
});
function RouteComponent() {
return (
<div>
Hello from authenticated route!
<Outlet />
</div>
);
}
routes/_authenticated/index.tsx
file
import { createFileRoute } from "@tanstack/react-router";
import logo from "../../logo.svg";
import "../../App.css";
export const Route = createFileRoute("/_authenticated/")({
component: AuthenticatedRoute,
});
function AuthenticatedRoute() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/routes/\_authenticated/index.tsx</code> and save to
reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
<a
className="App-link"
href="https://tanstack.com"
target="_blank"
rel="noopener noreferrer"
>
Learn TanStack
</a>
</header>
</div>
);
}
So, That’s it. We integrated supabase authentication with the tanstack router + vite
So if you wanna see the source code you can get it from my GitHub repository.
https://github.com/Your-Ehsan/tutorials/tree/setup-auth-in-react-with-tanstack-and-supabase
or download the code
git clone https://github.com/Your-Ehsan/tutorials.git --branch setup-auth-in-react-with-tanstack-and-supabase --single-branch supabase-auth-with-tanstack
Top comments (0)