1. 组件层面优化首屏加载速度
样式加载优化
- SSR 场景:
- 内联关键 CSS:在 Document 组件的
getInitialProps
方法中(用于 SSR),提取首屏渲染所需的关键 CSS 并内联到 HTML 中。这样浏览器在解析 HTML 时就能立即应用样式,减少首次渲染的白屏时间。例如,可以使用 styled - components
的 ServerStyleSheet
来提取样式:
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheet } from'styled - components';
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />)
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
)
};
} finally {
sheet.seal();
}
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
- 字体加载优化:在
Head
标签内使用 @font - face
规则,通过设置 font - display
属性来控制字体加载行为。例如,设置 font - display: swap
,在字体加载时先使用系统字体渲染文本,字体加载完成后再切换,避免文本闪烁。
<Head>
<style>
@font - face {
font - family: 'MyFont';
src: url('/fonts/myfont.woff2') format('woff2'),
url('/fonts/myfont.woff') format('woff');
font - display: swap;
}
</style>
</Head>
- SSG 场景:
- 预生成 CSS:在构建阶段(使用
next build
进行 SSG),生成所有页面的 CSS 文件,并通过 next.config.js
配置文件中的 cssMinimizer
进行压缩。这样在页面加载时,浏览器可以快速下载并解析 CSS。
- 代码拆分 CSS:使用工具如
mini - css - extract - plugin
将 CSS 从 JavaScript 中拆分出来,实现并行加载。在 Next.js 项目中,next - css
插件默认会将 CSS 提取到单独文件。这有助于提高首屏加载速度,因为浏览器可以同时请求 HTML、CSS 和 JavaScript 文件。
脚本注入优化
- SSR 场景:
- 异步加载非关键脚本:对于那些不影响首屏渲染的脚本,如第三方统计脚本或广告脚本,在 Document 组件的
render
方法中使用 script
标签,并设置 async
属性。这样脚本会在页面解析完成后异步加载,不会阻塞首屏渲染。
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
<script async src="https://example.com/analytics.js"></script>
</body>
</Html>
);
}
- 内联关键脚本:对于那些对首屏交互至关重要的脚本,如初始化页面交互的脚本,可以内联到 HTML 中。但要注意内联脚本不能过大,以免影响 HTML 的解析速度。
- SSG 场景:
- 动态导入脚本:对于某些脚本,只有在特定页面或用户交互时才需要,可以使用动态导入。例如,在 Next.js 中,可以使用
next/dynamic
动态导入组件,同时该组件依赖的脚本也会按需加载。这有助于减少初始页面加载的脚本体积。
import dynamic from 'next/dynamic';
const MyComponent = dynamic(() => import('../components/MyComponent'), {
ssr: false
});
2. 减少重复渲染
- SSR 场景:
- 缓存数据:在
getInitialProps
方法中,可以使用缓存机制来避免重复获取相同的数据。例如,可以使用 Node.js 的 lru - cache
库来缓存 API 响应数据。如果相同的数据请求再次发生,直接从缓存中获取,减少数据库或 API 的调用次数,从而减少重复渲染。
import LRU from 'lru - cache';
const cache = new LRU({
max: 100,
maxAge: 1000 * 60 * 5
});
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const key = JSON.stringify(ctx.query);
let data = cache.get(key);
if (!data) {
data = await fetchData(ctx.query);
cache.set(key, data);
}
return { data };
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
- Memoization:对于 Document 组件中的纯函数,如样式生成函数,可以使用
React.memo
或手动实现 memoization 来避免不必要的重新计算。这在组件属性没有变化时,防止重复渲染。
- SSG 场景:
- Incremental Static Regeneration:Next.js 支持增量静态再生。通过设置
revalidate
选项,可以在页面构建后定期重新生成静态页面。这有助于减少重复渲染,因为只有在数据发生变化时才会重新生成页面,而不是每次请求都重新渲染。例如,在页面组件中设置:
export async function getStaticProps({ params }) {
const data = await fetchData(params.id);
return {
props: { data },
revalidate: 60 * 60 * 24 // 一天后重新生成
};
}
- Static Props Caching:在构建阶段,Next.js 会缓存
getStaticProps
的结果。如果页面依赖的数据没有变化,后续构建不会重新执行 getStaticProps
,从而减少重复渲染。
3. 不同优化策略对 SSR 和 SSG 的影响
- 样式加载优化:
- SSR:内联关键 CSS 可以显著提高首屏加载速度,因为服务器端直接将样式注入 HTML,浏览器无需额外请求。但如果内联样式过多,会增加 HTML 的大小,影响解析速度。字体加载优化策略对 SSR 和 SSG 同样重要,都能提升用户体验。
- SSG:预生成和压缩 CSS 文件在 SSG 场景下效果很好,因为构建时生成的静态文件可以直接被浏览器快速加载。代码拆分 CSS 能充分利用浏览器的并行加载能力,进一步提高加载速度。但如果 CSS 文件过多或过大,可能会增加请求数量和总下载大小。
- 脚本注入优化:
- SSR:异步加载非关键脚本可以避免阻塞首屏渲染,但可能会导致脚本执行延迟,影响后续交互。内联关键脚本虽然能提高首屏交互速度,但过大的内联脚本会增加 HTML 解析时间。
- SSG:动态导入脚本在 SSG 中非常有效,因为它可以按需加载脚本,减少初始加载体积。但如果动态导入过于频繁,可能会增加请求次数,影响性能。
- 减少重复渲染:
- SSR:缓存数据和 memoization 可以有效减少重复渲染,提高服务器端渲染效率。但缓存管理需要注意数据的时效性,避免使用过期数据。
- SSG:增量静态再生和静态 props 缓存是 SSG 减少重复渲染的重要手段。增量静态再生确保页面数据的实时性,而静态 props 缓存则提高构建效率。但增量静态再生可能会增加服务器负载,需要合理设置
revalidate
时间。