Skip to main content
RapidDev - Software Development Agency
lovable-issues

Fixing Login Issues on Page Refresh in Lovable Applications

Login state disappearing on page refresh is caused by not listening for Supabase's auth session restoration. When the page reloads, Supabase automatically restores the session from localStorage, but your app must wait for this by setting up an onAuthStateChange listener in a useEffect. Without it, your auth check runs before the session is restored and incorrectly redirects to the login page. Add the listener in your root auth provider and show a loading state until the session is confirmed.

Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced9 min read~15 minAll Lovable versions with Supabase AuthMarch 2026RapidDev Engineering Team
TL;DR

Login state disappearing on page refresh is caused by not listening for Supabase's auth session restoration. When the page reloads, Supabase automatically restores the session from localStorage, but your app must wait for this by setting up an onAuthStateChange listener in a useEffect. Without it, your auth check runs before the session is restored and incorrectly redirects to the login page. Add the listener in your root auth provider and show a loading state until the session is confirmed.

Why auth state resets on page refresh in Lovable apps

Supabase Auth stores session tokens in the browser's localStorage. When you refresh the page, Supabase's client library reads these tokens and silently restores the session. However, this restoration is asynchronous — it does not happen instantly. If your app checks for a logged-in user before Supabase finishes restoring the session, it sees no user and redirects to the login page. This is the most common auth issue in Lovable projects. The AI typically generates authentication code that checks session state on component mount, but it often misses the timing gap between page load and session restoration. The result is a frustrating experience where users are logged in, refresh the page, and get kicked back to login. The fix is to use Supabase's onAuthStateChange listener, which fires whenever the auth state changes — including when the session is restored after a page refresh. By combining this listener with a loading state, your app waits for Supabase to confirm the session before deciding whether to show the dashboard or the login page.

  • Missing onAuthStateChange listener: the app checks session state before Supabase restores it from localStorage
  • Auth check runs synchronously on mount while session restoration is asynchronous
  • getSession() called once on mount but the result arrives after the component already redirected to login
  • Auth context provider does not track a loading/initializing state, causing premature redirects
  • Session tokens expired and Supabase cannot refresh them (refresh token also expired or was revoked)

Error messages you might see

User is redirected to login page on every page refresh despite being logged in

This is a behavior, not an error message. The app checks auth state before Supabase restores the session from localStorage. Add an onAuthStateChange listener and a loading state to wait for session restoration.

AuthSessionMissingError: Auth session missing!

Supabase could not find a valid session. This happens when the refresh token has expired (default 7 days of inactivity) or when localStorage was cleared. The user needs to log in again.

Invalid Refresh Token: Refresh Token Not Found

The stored refresh token is no longer valid on the Supabase server. This can happen after Supabase project restarts or if the token was manually revoked. Clear localStorage and redirect to login.

Before you start

  • A Lovable project using Supabase Auth (email/password, magic links, or OAuth)
  • An auth context or provider component that manages the current user state
  • At least one protected route that should only be accessible to logged-in users
  • Supabase project configured with correct Site URL (not localhost) in Supabase Dashboard → Authentication → URL Configuration

How to fix it

1

Add an onAuthStateChange listener in your auth provider

This listener fires when Supabase restores the session from localStorage after a page refresh

Open your auth context or provider component (typically at src/contexts/AuthContext.tsx or src/providers/AuthProvider.tsx). Add a useEffect that calls supabase.auth.onAuthStateChange(). This listener fires with the current session whenever the auth state changes — including the initial session restoration on page load. Store the user from the session in your state. The listener returns an unsubscribe function that you should call in the useEffect cleanup to prevent memory leaks.

Before
typescript
// AuthProvider.tsx — checks session once, misses async restoration
const AuthProvider = ({ children }: { children: React.ReactNode }) => {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
// This runs before Supabase restores the session
const { data } = supabase.auth.getSession();
// data.session is null because restoration hasn't finished
}, []);
return <AuthContext.Provider value={{ user }}>{children}</AuthContext.Provider>;
};
After
typescript
// AuthProvider.tsx — listens for auth state changes including restoration
const AuthProvider = ({ children }: { children: React.ReactNode }) => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true); // Wait for session check
useEffect(() => {
// Listen for all auth state changes (login, logout, token refresh, session restore)
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(_event, session) => {
setUser(session?.user ?? null);
setLoading(false); // Session check complete
}
);
// Cleanup listener on unmount to prevent memory leaks
return () => subscription.unsubscribe();
}, []);
return (
<AuthContext.Provider value={{ user, loading }}>
{children}
</AuthContext.Provider>
);
};

Expected result: The auth state updates correctly when Supabase restores the session. The loading flag lets other components wait before checking auth.

2

Show a loading state while the session is being restored

Without a loading state, the app assumes no user is logged in and redirects to login before the session is ready

In your protected route wrapper or layout component, check the loading flag from your auth context. While loading is true, show a spinner or loading screen instead of redirecting to the login page. Once loading becomes false, check the user state to decide whether to show the protected content or redirect. This prevents the flash-redirect-back-to-login problem.

Before
typescript
// ProtectedRoute.tsx — redirects immediately, no loading check
const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
const { user } = useAuth();
if (!user) {
return <Navigate to="/login" replace />;
// Redirects before session restoration completes
}
return <>{children}</>;
};
After
typescript
// ProtectedRoute.tsx — waits for session check before redirecting
const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
const { user, loading } = useAuth();
if (loading) {
// Show loading while Supabase restores the session from localStorage
return (
<div className="min-h-screen flex items-center justify-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" />
</div>
);
}
if (!user) {
return <Navigate to="/login" replace />;
}
return <>{children}</>;
};

Expected result: On page refresh, a brief loading spinner appears while Supabase restores the session. Once restored, the user sees their dashboard instead of being kicked to login.

3

Also fetch the initial session for faster first load

onAuthStateChange fires on changes, but calling getSession ensures you have the current state immediately if the session was already restored

Add a getSession call alongside your onAuthStateChange listener. This handles the case where the session was already restored before the listener was registered. Call getSession inside the same useEffect, and update the user state from its result. The onAuthStateChange listener then keeps the state updated for any subsequent changes (like token refreshes).

Before
typescript
// Only using onAuthStateChange — may miss already-restored session
useEffect(() => {
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(_event, session) => {
setUser(session?.user ?? null);
setLoading(false);
}
);
return () => subscription.unsubscribe();
}, []);
After
typescript
// Using both getSession and onAuthStateChange for complete coverage
useEffect(() => {
// Get current session (may already be restored from localStorage)
supabase.auth.getSession().then(({ data: { session } }) => {
setUser(session?.user ?? null);
setLoading(false);
});
// Listen for future auth changes (login, logout, token refresh)
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(_event, session) => {
setUser(session?.user ?? null);
}
);
return () => subscription.unsubscribe();
}, []);

Expected result: Auth state loads on the first render attempt. The loading spinner appears for the shortest possible time before confirming the session.

4

Verify Supabase Site URL is not set to localhost

If Site URL is localhost, session tokens may not work correctly on your published Lovable domain

In the Supabase Dashboard, go to Authentication → URL Configuration. Check that the Site URL is set to your published Lovable app URL (like https://your-app.lovable.app), not http://localhost:3000. Also add your Lovable preview URL and any custom domains to the Redirect URLs list. A mismatch between the Site URL and the actual app domain can cause session restoration to fail silently. If this configuration spans your Supabase dashboard and Lovable settings and feels complex, RapidDev's engineers have resolved this exact auth pattern across 600+ Lovable projects.

Expected result: Supabase Site URL matches your published app domain. Auth sessions persist correctly across page refreshes on both preview and published URLs.

Complete code example

src/contexts/AuthContext.tsx
1import { createContext, useContext, useEffect, useState } from "react";
2import { User } from "@supabase/supabase-js";
3import { supabase } from "@/integrations/supabase/client";
4
5interface AuthContextType {
6 user: User | null;
7 loading: boolean;
8 signOut: () => Promise<void>;
9}
10
11const AuthContext = createContext<AuthContextType>({
12 user: null,
13 loading: true,
14 signOut: async () => {},
15});
16
17export const useAuth = () => useContext(AuthContext);
18
19export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
20 const [user, setUser] = useState<User | null>(null);
21 const [loading, setLoading] = useState(true);
22
23 useEffect(() => {
24 // Fetch current session (handles already-restored sessions)
25 supabase.auth.getSession().then(({ data: { session } }) => {
26 setUser(session?.user ?? null);
27 setLoading(false);
28 });
29
30 // Listen for auth changes (login, logout, token refresh)
31 const { data: { subscription } } = supabase.auth.onAuthStateChange(
32 (_event, session) => {
33 setUser(session?.user ?? null);
34 }
35 );
36
37 return () => subscription.unsubscribe();
38 }, []);
39
40 const signOut = async () => {
41 await supabase.auth.signOut();
42 setUser(null);
43 };
44
45 return (
46 <AuthContext.Provider value={{ user, loading, signOut }}>
47 {children}
48 </AuthContext.Provider>
49 );
50};

Best practices to prevent this

  • Always use onAuthStateChange to track session changes — never rely on a one-time getSession check alone
  • Add a loading state to your auth provider and check it in protected routes before redirecting to login
  • Set Supabase Site URL to your published app domain, not localhost, in Supabase Dashboard → Authentication → URL Configuration
  • Call supabase.auth.getSession() alongside the listener for faster initial session detection
  • Clean up the onAuthStateChange subscription in your useEffect return function to prevent memory leaks
  • Add all deployment URLs (lovable.app preview, published URL, custom domain) to Supabase's Redirect URLs list
  • Handle expired sessions gracefully — show a re-login prompt instead of a cryptic error page
  • Test auth persistence in incognito mode to simulate a clean browser state without cached sessions

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

My Lovable (lovable.dev) project uses Supabase Auth and the user gets logged out on every page refresh. The project uses React + TypeScript + Supabase. Here is my current auth context/provider: [paste your AuthContext or AuthProvider code here] And here is my protected route component: [paste your ProtectedRoute code here] Please: 1. Add an onAuthStateChange listener that handles session restoration after page refresh 2. Add a loading state so protected routes wait for the session check before redirecting 3. Also include a getSession call for faster initial load 4. Make sure the listener is properly cleaned up on unmount

Lovable Prompt

Users are being redirected to the login page on every page refresh even though they are logged in. Please fix @src/contexts/AuthContext.tsx to add a Supabase onAuthStateChange listener that tracks session restoration, add a loading state that starts as true and becomes false after the session is checked, and update @src/components/ProtectedRoute.tsx to show a loading spinner while loading is true instead of immediately redirecting to login.

Frequently asked questions

Why does my Lovable login page appear on every refresh?

Your app is checking for a logged-in user before Supabase finishes restoring the session from localStorage. Add an onAuthStateChange listener in your auth provider and a loading state that prevents redirects until the session check completes.

How does Supabase store auth sessions in the browser?

Supabase stores access and refresh tokens in localStorage. On page load, the Supabase client reads these tokens and verifies them with the server. This process is asynchronous, so your app must wait for it to finish before checking auth state.

What is onAuthStateChange and why do I need it?

onAuthStateChange is a Supabase listener that fires whenever the auth state changes — when a user logs in, logs out, when a token is refreshed, and when the session is restored after a page reload. It is the recommended way to keep your app's auth state synchronized.

How long do Supabase sessions last?

By default, Supabase access tokens expire after 1 hour, but the client automatically refreshes them using the refresh token. Refresh tokens last for about 7 days of inactivity. If a user does not visit your app for 7+ days, they will need to log in again.

Do I need to configure anything in the Supabase Dashboard?

Yes. Go to Authentication → URL Configuration and set the Site URL to your published Lovable app URL (not localhost). Add all your deployment URLs to the Redirect URLs list with wildcards: https://your-app.lovable.app/**. This ensures sessions work across preview and published environments.

Why does auth work in preview but break on the published site?

The Supabase Site URL or Redirect URLs may only include the preview domain but not the published lovable.app domain. Add both URLs to the Redirect URLs in Supabase Dashboard → Authentication → URL Configuration.

What if I can't fix auth persistence myself?

Auth session management involves coordinating your React context, Supabase configuration, and hosting redirect URLs. If the issue spans multiple components and settings, RapidDev's engineers have debugged this exact auth persistence pattern across 600+ Lovable projects.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your issue.

Book a free consultation

Need help with your Lovable project?

Our experts have built 600+ apps and can solve your issue fast. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.