NextJs
中,通过script
,引入lib-flexible
,在client计算rem
背景
在 Next.js
中,我们通常会在页面中引入一些 client script
,比如 Google Analytics
,lib-flexible
或者一些自定义的脚本。但是,这些脚本通常需要在页面加载后才能执行,否则可能会出现错误。因此,我们需要在页面加载后,动态地引入这些脚本。本本主要是在ssr
场景下提供rem
布局在client
端布局闪烁问题提供一个解决方案。
解决方案
核心思想:通过next/script
来实现preload script
,然后通过cookie
来读取fontSize
,实现ssr
场景布局闪烁问题
上述方案也有一个问题,第一次加载,无法读取
fontSize
,希望大家可以先思考一下有什么更好的解决方案,在文末会提供一种解决方案
本次主要有两种写法供大家参考
第一种写法
直接通过 Script
标签引入,然后在script
里面去设置cookie
就可以了
// client script
(()=> {
//... 计算rem
const rem = 16;
// 设置cookie
document.cookie = 'fontSize=' + rem+'px; ';
})(window, document)
// app/layout.tsx
import Script from 'next/script'
import { cookies } from 'next/headers'
import type { Metadata } from 'next'
import Layout from '@/components/Layout'
import './globals.css'
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default async function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
const cookieStore = await cookies() // 读取cookie中的fontSize,
const fontSize = cookieStore.get('fontSize')?.value
return (
<html lang="en" style={{ fontSize: fontSize }}>
<head>
<Script id="analytics-script" src="flexble.js" />
</head>
<body>
<Layout>{children}</Layout>
</body>
</html>
)
}
到此,这样就可以解决了,只是会出现上面提到的问题,这样布局闪烁总是给人是个bug
的感觉,与其解决bug
,倒不如隐藏起来,这样大家就看不到了,看不见就当不存在了。
思路: 在第一次没有拿到fontSize
的时候,我们可以给html
或者body
设置一个opacity:0
,这样就可以先隐藏起来了,然后在script
里面,通过onload
时间里面,把html
或者body
的opacity
设置为1,这样就可以解决上面的问题了。
// client script
(()=> {
//... 计算rem
const rem = 16;
// 设置cookie
document.cookie = 'fontSize=' + rem+'px; ';
document.style.opacity = 1
})(window, document)
// app/layout.tsx
import Script from 'next/script'
import { cookies } from 'next/headers'
import type { Metadata } from 'next'
import Layout from '@/components/Layout'
import './globals.css'
import Client from '@/components/Client/index'
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default async function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
const cookieStore = await cookies()
const fontSize = cookieStore.get('fontSize')?.value
return (
<html lang="en" style={{ fontSize: fontSize, opacity: fontSize === undefined ? 0 : 1 }}>
<head>
<Script id="analytics-script" src="flexble.js" />
</head>
<body>
<Client scripts={[{ src: 'flexble.js' }]} />
<Layout>{children}</Layout>
</body>
</html>
)
}
第二种写法
解决方案都是一样的,我这里只是拓展一下,写法大同小异,只是封装了一个DynamicComponentWithScript
组件,方便以后使用
// 这里必须通过 'use client' 进行标示是客户端组件,如果没有标示,可以通过dynamic 动态引入,不过也需要将ssr标示为false
'use client'
import Script, { ScriptProps } from 'next/script'
import { type PropsWithChildren } from 'react'
interface Props extends PropsWithChildren {
scripts: ScriptProps[]
}
function DynamicComponentWithScript({ scripts, children }: Props) {
return (
<>
{scripts.map((script, index) => {
return <Script {...script} key={index} />
})}
{children}
</>
)
}
export default DynamicComponentWithScript
写在最后
⚠️ 以上是一种取巧的解决方案,其实也不是一个优雅的或合理的解决方案,因为设置透明度为0 再到1 这个过程中有gap,会拖慢整个白屏时间,所以也不是个优雅的解决方案
但是目前没有想到其他更好的解决方案,如果大家有更好的解决方案,希望可以在评论区讨论