On a Next.js project, I'd like to get some initial HTML with this exact same content inside the <head>
:
<link href="..." rel="stylesheet" media="print" onload="this.media='all'" />
What I have in my code, inside Next.js's <Head>
component, is:
{ /* @ts-ignore */ }
<link href="..." rel="stylesheet" media="print" onload="this.media='all'" />
Without the @ts-ignore
it says:
Property 'onload' does not exist on type 'DetailedHTMLProps<LinkHTMLAttributes, HTMLLinkElement>'. Did you mean 'onLoad'? ts(2322)
And if I use onLoad
instead of onload
I get:
Type 'string' is not assignable to type '(event: SyntheticEvent<HTMLLinkElement, Event>) => void'. ts(2322)
The problem is that the server-side generated HTML that I get has:
<link href="..." rel="stylesheet" media="print" />
And only once the page has rehydrated it updates to:
<link href="..." rel="stylesheet" media="all" onload="this.media='all'">
I've found this issue on GitHub but it doesn't help as I'm not using Google Fonts but Typography.com, so I can't use next-google-fonts
: https://github.com/vercel/next.js/issues/12984
I'm thinking about adding a ref
to that link
tag and setting the attribute using setAttribute
, which will hopefully work on the server-side as well, but wondering if there's a simpler way to do it.
Updated answer (Next 13-14):
Next.js now offers Google and local fonts optimization out of the box. Here's how to load Google fonts:
// This will add the Inter font to your own deployment, serving
// them from your domain without sending any data to Google:
import { Inter } from 'next/font/google';
// If loading a variable font, you don't need to specify the font weight
const inter = Inter({
subsets: ['latin'],
display: 'swap',
})
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className={inter.className}>
<body>{children}</body>
</html>
)
}
Original answer (Next 12):
So I eventually fixed this using a <style>
tag with dangerouslySetInnerHTML
in a custom _document.js
. All together it should look like this:
<link rel="preconnect" href="https://fonts.googleapis.com" crossOrigin="anonymous" />
<link rel="preload" href="https://fonts.googleapis.com/css2?family=Inconsolata:wght@400;600&family=Karla:wght@700&display=swap" as="style" />
<style dangerouslySetInnerHTML={ {
__html: `</style>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inconsolata:wght@400;600&family=Karla:wght@700&display=swap"
media="print"
onload="this.media='all';"
/>
<style>`
} }></style>
<noscript>
<link
rel="stylesheet"
type="text/css"
href="https://fonts.googleapis.com/css2?family=Inconsolata:wght@400;600&family=Karla:wght@700&display=swap" />
</noscript>
Which generates the following output:
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin="anonymous"/>
<link rel="preload" href="https://fonts.googleapis.com/css2?family=Inconsolata:wght@400;600&family=Karla:wght@700&display=swap" as="style"/>
<style></style>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inconsolata:wght@400;600&family=Karla:wght@700&display=swap" media="print" onload="this.media='all';" />
<style></style>
<noscript><link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css2?family=Inconsolata:wght@400;600&family=Karla:wght@700&display=swap"/></noscript>
Not pretty, but better than having a <div>
inside the <head>
(which is not interpreted correctly by all browsers).
There's an open RFC to create a RawHTML
component or extend Fragment
to accept dangerouslySetInnerHTML
so that something like this is possible without hacks, but it's been more than a year since it was created.
Also, there's quite a long discussion about this as well with a few different solutions (hacks) that seem to work.
You can see the solution working here: https://andorratechvalley.com/