supabase / auth-helpers

A collection of framework specific Auth utilities for working with Supabase.
https://supabase.github.io/auth-helpers/
MIT License
902 stars 237 forks source link

Next.js 13 App flickering issues when logged in with nextjs auth helper. #541

Closed colehendricks3 closed 1 year ago

colehendricks3 commented 1 year ago

Bug report

Describe the bug

When a user is logged into my application and you change browser tabs away and to the application, all the content flickers as if it was reloading. The layout.css file that next.js renders for the whole app shell keeps updating in the DOM with a new version number each time and it even keeps putting a new stylesheet in the HEAD section of the DOM. On top of this, any time I go from another tab back to my application in my browser, a new GET request is made in my dev console to that page.

I followed the docs to a T from https://supabase.com/docs/guides/auth/auth-helpers/nextjs-server-components and this issue only happens in a logged in state. I have never seen this happen in a NEXT.js app before and this is my first time using this auth library. I tried removing Tailwind and any other library that I thought could cause the issue but they all persisted with the one common denominator being this library.

I am really banging my head against the wall to fix this and was hoping I could get some help from someone for what I can try to resolve this.

https://www.loom.com/share/c0b90d908cfe438a94d3c2e72ddb8bce

Here's my overall app layout wrapper.

import "./globals.css"
import { Merriweather } from "next/font/google"
import SupabaseProvider from "./supabase-provider"
import Navigation from "./Navigation"

const merriweather = Merriweather({
  variable: "--font-merriweather",
  subsets: ["latin"],
  weight: ["300", "400", "700", "900"],
})

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body
        className={`${merriweather.variable} min-h-screen bg-white text-slate-900 font-serif antialiased`}
      >
        <SupabaseProvider>
          <Navigation />
          <main>{children}</main>
        </SupabaseProvider>
      </body>
    </html>
  )
}

And my supabase-provider.

"use client"

import { createContext, useContext, useEffect, useState } from "react"
import { createBrowserSupabaseClient } from "@supabase/auth-helpers-nextjs"
import { useRouter } from "next/navigation"

const Context = createContext(undefined)

export default function SupabaseProvider({ children, session }) {
  const [supabase] = useState(() => createBrowserSupabaseClient())
  const router = useRouter()

  useEffect(() => {
    const {
      data: { subscription },
    } = supabase.auth.onAuthStateChange(() => {
      router.refresh()
    })

    return () => {
      subscription.unsubscribe()
    }
  }, [router, supabase])

  return (
    <Context.Provider value={{ supabase, session }}>
      <>{children}</>
    </Context.Provider>
  )
}

export const useSupabase = () => {
  const context = useContext(Context)

  if (context === undefined) {
    throw new Error("useSupabase must be used inside SupabaseProvider")
  }

  return context
}

System information

silentworks commented 1 year ago

Wrap your router.refresh() in a if block to check if the access token has changed.

const {
  data: { subscription },
} = supabase.auth.onAuthStateChange((_, _session) => {
  if (_session?.access_token !== session?.access_token) {
    router.refresh();
  }
});
colehendricks3 commented 1 year ago

@silentworks Thank you! I also noticed that I wasn't passing in a session prop to the component itself in the layout file. I really appreciate the help.

Here is the updated layout and provider files.

app/layout.js

import "./globals.css"
import { Merriweather } from "next/font/google"
import SupabaseProvider from "./supabase-provider"
import Navigation from "./Navigation"
import { createClient } from "@/utils/supabase-server"

const merriweather = Merriweather({
  variable: "--font-merriweather",
  subsets: ["latin"],
  weight: ["300", "400", "700", "900"],
})

export default async function RootLayout({ children }) {
  const supabase = createClient()
  const {
    data: { session },
  } = await supabase.auth.getSession()
  return (
    <html lang="en">
      <body
        className={`${merriweather.variable} min-h-screen bg-white text-slate-900 font-serif antialiased`}
      >
        <SupabaseProvider session={session}>
          <Navigation />
          <main>{children}</main>
        </SupabaseProvider>
      </body>
    </html>
  )
}

app/supabase-provider

"use client"

import { createContext, useContext, useEffect, useState } from "react"
import { createBrowserSupabaseClient } from "@supabase/auth-helpers-nextjs"
import { useRouter } from "next/navigation"

const Context = createContext(undefined)

export default function SupabaseProvider({ children, session }) {
  const [supabase] = useState(() => createBrowserSupabaseClient())
  const router = useRouter()

  useEffect(() => {
    const {
      data: { subscription },
    } = supabase.auth.onAuthStateChange((_, _session) => {
      if (_session?.access_token !== session?.access_token) {
        router.refresh()
      }
    })

    return () => {
      subscription.unsubscribe()
    }
  }, [router, supabase, session?.access_token])

  return (
    <Context.Provider value={{ supabase, session }}>
      <>{children}</>
    </Context.Provider>
  )
}

export const useSupabase = () => {
  const context = useContext(Context)

  if (context === undefined) {
    throw new Error("useSupabase must be used inside SupabaseProvider")
  }

  return context
}