-
Notifications
You must be signed in to change notification settings - Fork 27.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RootLayout static Metadata & Viewport flickers on client navigation to dynamic page with loading.tsx #75334
Comments
My "Wrapper" fix looks like this... /app/todos/page.tsximport { auth } from "@/lib/auth"
import { headers } from "next/headers"
import { HydrationBoundary, dehydrate } from "@tanstack/react-query"
import { prefetchFindMany } from "@/lib/prefetch-queries"
import { getQueryClient } from "../../get-query-client"
import { Locale } from "next-intl"
import { redirect } from "@/i18n/routing"
import { sql } from "drizzle-orm"
import TodosClient from "./client"
import { Suspense } from "react"
export default function PageWrapper(
{ params }: { params: Promise<{ locale: Locale }> }
) {
return (
<Suspense fallback={<TodosSkeleton />}>
<TodosPage params={params} />
</Suspense>
)
}
async function TodosPage(
{ params }: { params: Promise<{ locale: Locale }> }
) {
const queryClient = getQueryClient()
const session = await auth.api.getSession({
headers: await headers()
})
if (!session) {
const { locale } = await params
return redirect({ locale, href: "/auth/login?callbackURL=/todos" })
}
await prefetchFindMany(queryClient, "todos", { where: sql`user_id = ${session.user.id}` })
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<TodosClient />
</HydrationBoundary>
)
} Before I was able to just export TodosPage and use a loading.tsx, but that causes metadata flickering in production. I had to delete loading.tsx and create PageWrapper to fix it. /app/layout.tsximport { ReactNode } from "react"
import { RootProviders } from "./root-providers"
import "@/styles/globals.css"
export const viewport: Viewport = {
themeColor: [
{ media: "(prefers-color-scheme: light)", color: "#e0e7ff" },
{ media: "(prefers-color-scheme: dark)", color: "#1e1b4b" },
],
}
export const metadata = {
title: "NEW-TECH"
}
export default function RootLayout(
{ children }: { children: ReactNode }
) {
return (
<RootProviders>
{children}
</RootProviders>
)
} My biggest issue is that my viewport themeColors were flickering with loading.tsx, and I allow users to change their theme from System and then I use javascript to modify themeColors on the front end. But the title flickering also seems buggy. Hoping this can be fixed so I can go back to using loading.tsx for dynamic pages |
Here is the providers I'm using for next-themes and then dynamically updating meta themeColor to match the user's setting for PWA's "use client"
import { ReactNode, useEffect } from "react"
import { ThemeProvider, useTheme } from "next-themes"
export function Providers(
{ children }: { children?: ReactNode }
) {
const { data: token } = useTokenQuery()
return (
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<MetaTheme />
{children}
</ThemeProvider>
)
}
function MetaTheme() {
const { resolvedTheme } = useTheme()
useEffect(() => {
const bgColor = resolvedTheme === "dark" ? "#1e1b4b" : "#e0e7ff"
document.querySelectorAll("meta[name='theme-color']")
.forEach((meta) => {
meta.setAttribute("content", bgColor)
})
}, [resolvedTheme])
return <></>
} |
Link to the code that reproduces this issue
https://codesandbox.io/p/devbox/kind-dream-ppgptn
To Reproduce
When using the App Router, if you have a RootLayout with static metadata (viewport, title), it works great when navigating across static pages. The metadata never changes, the title remains the same for all subpages of that layout.
As soon as you create a dynamic page (e.g. await connection()) then navigate to that page using a component, and create a "loading.tsx" for that dynamic page, it will Suspend the static metadata when you navigate to it, this causes the title to flicker. I was just ignoring at first, but when I tested my PWA I noticed my viewport theme was flickering, which was causing jarring issues since I have dynamic changes to the themeColors in my client (next-themes)
This led me down the rabbit hole to try to figure out why my parent static Layout wasn't getting immediately rendered. I discovered that the solution was indeed "loading.tsx". As soon as I delete loading.tsx, the metadata stopped flickering. I had to create a "Suspense wrapper" around my dynamic page to fix it. This works, but it adds extra boilerplate code to every dynamic page. It also means I cannot benefit from using "loading.tsx" at all in my project, since that will force the static RootLayout's metadata & viewport to be suspended, causing flickers.
Current vs. Expected behavior
Current:
Dynamic async pages cause reload flickers of parent static RootLayout metadata & viewport when loading.tsx is present.
Expected:
loading.tsx should not suspend or clear/reload the RootLayout's static metadata & viewport, loading.tsx should only suspend the page itself.
This bug only happens after builds, not during next dev
Provide environment information
Production App Router Next.js 15.1.6 & canary
Which area(s) are affected? (Select all that apply)
Metadata, Navigation
Which stage(s) are affected? (Select all that apply)
next start (local), Vercel (Deployed)
Additional context
No response
The text was updated successfully, but these errors were encountered: