Remix Loaders allow us to fetch data server-side, before rendering a component. This works great if you are implementing your authorization rules for data fetching in the loader function, however, Supabase allows us to use Row Level Security policies to write access policies alongside the data in the database.
By default, supabase-js
stores session data in localStorage
, which exists only within the user's browser. If we want this session to be available within Loader or Action functions in Remix, we need to store the session in a cookie. Cookies are automatically sent with every request to the server.
In this lesson, we look at using the Supabase Auth Helpers package for Remix to automate this process, and swap out the storage mechanism for the Supabase client, to use cookies to store session data.
Additionally, we refactor our application to use the new createServerClient
and createBrowserClient
functions, making cookies the single source of truth about the user's current session, across the server and client-side of our Remix app.
Install Remix Auth Helpers
npm i @supabase/auth-helpers-remix
Create server-side Supabase client
import { createServerClient } from "@supabase/auth-helpers-remix";
import type { Database } from "db_types";
export default ({
request,
response,
}: {
request: Request;
response: Response;
}) =>
createServerClient<Database>(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!,
{ request, response }
);
Create client-side Supabase client (simplified)
import { useState } from "react";
import { createBrowserClient } from "@supabase/auth-helpers-remix";
export const loader = async ({ request }: LoaderArgs) => {
const env = {
SUPABASE_URL: process.env.SUPABASE_URL!,
SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY!,
};
return json({ env });
};
const { env, session } = useLoaderData<typeof loader>();
const [supabase] = useState(() =>
createBrowserClient<Database>(env.SUPABASE_URL, env.SUPABASE_ANON_KEY)
);
return <Outlet context={{ supabase }} />;
Use Supabase in a loader
export const loader = async ({ request }: LoaderArgs) => {
const response = new Response();
const supabase = createServerSupabase({ request, response });
const { data } = await supabase.from("messages").select();
return json({ data }, { headers: response.headers });
};
Use Supabase in an action
export const action = async ({ request }: ActionArgs) => {
const response = new Response();
const supabase = createServerSupabase({ request, response });
const { data } = await supabase.from("messages").insert({ content: "hello" });
return json({ data }, { headers: response.headers });
};
Use Supabase in a component
import { useOutletContext } from "@remix-run/react";
import type { SupabaseOutletContext } from "~/root";
export default function Login() {
const { supabase } = useOutletContext<SupabaseOutletContext>();
const handleLogin = async () => {
await supabase.auth.signInWithOAuth({
provider: "github",
});
};
return <button onClick={handleLogin}>Login</button>;
}