vercel / next.js

The React Framework
https://nextjs.org
MIT License
127.47k stars 27.04k forks source link

next/font causes flickering during HMR on Firefox #52159

Open arvl130 opened 1 year ago

arvl130 commented 1 year ago

Verify canary release

Provide environment information

Operating System:
      Platform: linux
      Arch: x64
      Version: #1 ZEN SMP PREEMPT_DYNAMIC Sat, 01 Jul 2023 16:17:04 +0000
    Binaries:
      Node: 18.16.0
      npm: 8.19.2
      Yarn: 1.22.19
      pnpm: 8.6.2
    Relevant Packages:
      next: 13.4.9-canary.0
      eslint-config-next: N/A
      react: 18.2.0
      react-dom: 18.2.0
      typescript: 4.9.4
    Next.js Config:
      output: N/A

Which area(s) of Next.js are affected? (leave empty if unsure)

App directory (appDir: true), Font optimization (next/font)

Link to the code that reproduces this issue or a replay of the bug

https://github.com/arvl130/nextjs-bug-repro-flicker-on-hmr

To Reproduce

  1. Run the dev server in a terminal and open the homepage in Firefox. Wait for the page to fully load.

  2. Once the page has loaded, open another terminal and run the helper script included in the repository:

node update-page-every-one-second.mjs
  1. Flickering should appear (see video below).

Describe the Bug

If you use next/font on your Next.js app, during development, all text on your page will briefly disappear whenever HMR is triggered. This does not happen with the Pages dir. It also does not happen when developing with Chrome.

https://github.com/vercel/next.js/assets/59502616/681849c8-d44d-443d-bedf-f6c92f08698d

I can reproduce the issue on both Windows and Linux.

Expected Behavior

Text should not flicker when HMR is triggered, like in the Pages dir.

Which browser are you using? (if relevant)

Firefox 114.0.2

How are you deploying your application? (if relevant)

No response

arvl130 commented 9 months ago

Kind of disappointing to see no activity here after more than six months of being reported. To anyone else facing this issue, here is the workaround I'm using.

I found that the normal way of importing fonts (with @import) from Google Fonts doesn't cause any flickering. So during development, that's the method I use to load my fonts. I only use next/font in production.

You can conditionally change the way you load fonts by creating a Fonts component like this:

"use client"

import { Inter } from "next/font/google"
import { useServerInsertedHTML } from "next/navigation"

const inter = Inter({
  subsets: ["latin"],
  weight: ["400", "500", "600", "700"],
  display: "swap",
})

export function Fonts() {
  useServerInsertedHTML(() => {
    // Load fonts with @import during development due to a `next/font` bug.
    // For more info, see: https://github.com/vercel/next.js/issues/52159
    if (process.env.NODE_ENV === "development")
      return (
        <style
          dangerouslySetInnerHTML={{
            __html: `
              @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
            `,
          }}
        />
      )

    return (
      <style
        dangerouslySetInnerHTML={{
          __html: `
            :root {
              --font-inter: ${inter.style.fontFamily};
            }
          `,
        }}
      />
    )
  })

  return <></>
}

Remove next/font from your root layout. Then, import your Fonts component above the HTML <body> element:

import type { Metadata } from "next"
import "./globals.css"
import { Fonts } from "./fonts"

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
}

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="en">
      <Fonts />
      <body>{children}</body>
    </html>
  )
}

In my projects, I typically register my default sans font in font-sans. If you're doing something similar, make sure to conditionally register your fonts in there as well.

import type { Config } from "tailwindcss"
import { fontFamily } from "tailwindcss/defaultTheme"

const config: Config = {
  content: [
    "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      backgroundImage: {
        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
        "gradient-conic":
          "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
      },
      fontFamily: {
        sans: [
          process.env.NODE_ENV === "development"
            ? "Inter"
            : "var(--font-inter)",
          ...fontFamily.sans,
        ],
      },
    },
  },
  plugins: [],
}
export default config

Hopefully, we can get more attention from the Next.js Team in the near feature. This issue isn't particularly hard to reproduce. It's a very obvious bug when developing with Firefox.

arvl130 commented 4 months ago

Small update: Starting in Next.js version 14.x, it looks like this issue won't appear if you use Turbopack in development.