Skip to content
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

Open
daveycodez opened this issue Jan 26, 2025 · 2 comments
Labels
Metadata Related to Next.js' Metadata API.

Comments

@daveycodez
Copy link

daveycodez commented Jan 26, 2025

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

@github-actions github-actions bot added Metadata Related to Next.js' Metadata API. Linking and Navigating Related to Next.js linking (e.g., <Link>) and navigation. labels Jan 26, 2025
@daveycodez
Copy link
Author

daveycodez commented Jan 26, 2025

My "Wrapper" fix looks like this...

/app/todos/page.tsx

import { 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.tsx

import { 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

@daveycodez
Copy link
Author

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 <></>
}

@daveycodez daveycodez changed the title RootLayout static Metadata flickers on client navigation to dynamic page with loading.tsx RootLayout static Metadata & Viewport flickers on client navigation to dynamic page with loading.tsx Jan 26, 2025
@samcx samcx removed the Linking and Navigating Related to Next.js linking (e.g., <Link>) and navigation. label Jan 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Metadata Related to Next.js' Metadata API.
Projects
None yet
Development

No branches or pull requests

2 participants