How to setup the Supabase authentication with Tanstack Router in Vite React.
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