Bootstrap Remix app with Supabase Auth
Provides code snippets and instructions for adding Supabase authentication to a Remix application with TypeScript.
What this file does
Provides code snippets and instructions for adding Supabase authentication to a Remix application with TypeScript.
When to use it
- Starting a new Remix project that needs Supabase auth
- Adding authentication to an existing Remix app
- Setting up route protection with Supabase sessions
- Learning the Supabase auth helpers for Remix pattern
Assumes this stack
title: "Bootstrap Remix app with Supabase Auth" description: "Guidelines for writing Remix apps with Supabase Auth" tags: ["Remix", "Supabase", "Authentication", "Setup", "Frontend"] category: "Coding" author: "Csaba Farkas" slug: "bootstrap-remix-app-with-supabase-auth" id: "coding-144" seo_keywords: "Remix Supabase integration, Supabase setup, Remix authentication, Server-side rendering, Remix loaders" date: "2025-07-13" difficulty: "Advanced" schema_type: "TechArticle"
Bootstrap Remix app with Supabase Auth
Guidelines for writing Remix apps with Supabase Auth
Overview of implementing Supabase Auth
- Install @supabase/supabase-js and @supabase/auth-helpers-remix packages
- Configure Supabase client
- Set up root authentication loader
- Implement authentication components
- Add route protection
Critical Instructions for AI Language Models
As an AI language model, you MUST follow these guidelines when implementing Supabase with Remix:
- Always use the latest Supabase packages
- Implement proper TypeScript types for type safety
- Handle environment variables securely
- Follow Remix best practices and patterns
- Implement proper error handling
Correct Server Configuration
// app/utils/supabase.server.ts
import { createServerClient } from '@supabase/auth-helpers-remix'
import type { Database } from '~/types/supabase'
export function createSupabaseServerClient({ request, response }: {
request: Request
response: Response
}) {
return createServerClient<Database>(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!,
{ request, response }
)
}
export async function requireUser(request: Request) {
const response = new Response()
const supabase = createSupabaseServerClient({ request, response })
const { data: { session } } = await supabase.auth.getSession()
if (!session) {
throw redirect('/login', {
headers: response.headers
})
}
return {
user: session.user,
response
}
}
Correct Root Configuration
// app/root.tsx
import { json, type LoaderFunctionArgs } from '@remix-run/node'
import { useLoaderData, useRevalidator } from '@remix-run/react'
import { createBrowserClient } from '@supabase/auth-helpers-remix'
import { useState, useEffect } from 'react'
import type { Database } from '~/types/supabase'
import { createSupabaseServerClient } from '~/utils/supabase.server'
export async function loader({ request }: LoaderFunctionArgs) {
const response = new Response()
const supabase = createSupabaseServerClient({ request, response })
const { data: { session } } = await supabase.auth.getSession()
return json(
{
session,
env: {
SUPABASE_URL: process.env.SUPABASE_URL!,
SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY!
}
},
{ headers: response.headers }
)
}
export default function Root() {
const { env, session } = useLoaderData<typeof loader>()
const revalidator = useRevalidator()
const [supabase] = useState(() =>
createBrowserClient<Database>(env.SUPABASE_URL, env.SUPABASE_ANON_KEY)
)
useEffect(() => {
const {
data: { subscription }
} = supabase.auth.onAuthStateChange((event, session) => {
if (session?.access_token !== session?.access_token) {
revalidator.revalidate()
}
})
return () => subscription.unsubscribe()
}, [supabase, revalidator])
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
<Outlet context={{ supabase, session }} />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
)
}
Correct Authentication Components
// app/routes/auth.tsx
import { json, redirect, type ActionFunctionArgs } from '@remix-run/node'
import { Form, useActionData, useOutletContext } from '@remix-run/react'
import { createSupabaseServerClient } from '~/utils/supabase.server'
export async function action({ request }: ActionFunctionArgs) {
const response = new Response()
const supabase = createSupabaseServerClient({ request, response })
const formData = await request.formData()
const intent = formData.get('intent')
try {
if (intent === 'signup') {
const { error } = await supabase.auth.signUp({
email: formData.get('email') as string,
password: formData.get('password') as string
})
if (error) throw error
} else if (intent === 'signin') {
const { error } = await supabase.auth.signInWithPassword({
email: formData.get('email') as string,
password: formData.get('password') as string
})
if (error) throw error
} else if (intent === 'signout') {
const { error } = await supabase.auth.signOut()
if (error) throw error
}
return redirect('/', {
headers: response.headers
})
} catch (error) {
return json(
{ error: error.message },
{
status: 400,
headers: response.headers
}
)
}
}
export default function Auth() {
const { session } = useOutletContext()
const actionData = useActionData<typeof action>()
if (session) {
return (
<Form method="post">
<p>Welcome, {session.user.email}</p>
<button type="submit" name="intent" value="signout">
Sign Out
</button>
</Form>
)
}
return (
<div>
{actionData?.error && <p>{actionData.error}</p>}
<Form method="post">
<input
type="email"
name="email"
placeholder="Email"
required
/>
<input
type="password"
name="password"
placeholder="Password"
required
/>
<button type="submit" name="intent" value="signin">
Sign In
</button>
<button type="submit" name="intent" value="signup">
Sign Up
</button>
</Form>
</div>
)
}
Route Protection Implementation
// app/routes/dashboard.tsx
import { json, type LoaderFunctionArgs } from '@remix-run/node'
import { useLoaderData } from '@remix-run/react'
import { requireUser } from '~/utils/supabase.server'
export async function loader({ request }: LoaderFunctionArgs) {
const { user, response } = await requireUser(request)
return json({ user }, { headers: response.headers })
}
export default function Dashboard() {
const { user } = useLoaderData<typeof loader>()
return (
<div>
<h1>Dashboard</h1>
<p>Welcome, {user.email}</p>
</div>
)
}
Environment Variables Setup
Create a .env file:
SUPABASE_URL=your-project-url
SUPABASE_ANON_KEY=your-anon-key
AI Model Verification Steps
Before generating any code, you MUST verify:
- Is TypeScript properly configured?
- Are environment variables properly handled?
- Is error handling implemented?
- Are authentication state and user data properly typed?
- Is route protection configured correctly?
Consequences of Incorrect Implementation
If you generate code incorrectly:
- Type safety will be compromised
- Authentication flows may fail
- Security vulnerabilities may be introduced
- Route protection may be bypassed
- User data may be exposed
AI Model Response Template
When implementing Supabase Auth for Remix, you MUST:
- Use TypeScript for type safety
- Implement proper error handling
- Follow Remix loader/action patterns
- Configure secure session management
- Handle environment variables properly
What's inside
7 sections, 4 code examples, 2 configuration blocks, and a verification checklist
Change this for your project
- Replace
your-project-urlandyour-anon-keyin the.envexample with your Supabase project credentials - Replace
~/types/supabaseimport path with your actual generated types file location
Where it goes
Keep it in your repository where the agent or team that needs it will read it.
Worth borrowing
- Using a
requireUserhelper that throws a redirect when no session exists - Passing the Supabase client through Remix's
Outletcontext for child routes
Related Documents
How you work
Defines personality, planning, task execution, and communication conventions for a coding agent in the Codex CLI environment.
内置 Agent 提示词
Documents the system prompts, tool permissions, and model assignments for six built-in subagents in Claude Code.
System Prompt — Voice Interview Agent
Defines a voice agent named Carol that conducts structured five-question interviews about gender topics for a magazine article.
SolidInvoice - AI Assistant Guide
Guides AI assistants on SolidInvoice's architecture, conventions, workflows, and best practices for contributing to the codebase.