如何优化第三方脚本的加载?next/script 组件的 strategy 属性(beforeInteractive, afterInteractive, lazyOnLoad)有何区别?
next/script 组件概述
Next.js 15 的 next/script 组件提供了智能的第三方脚本加载优化,通过不同的加载策略来平衡性能和功能需求。
import Script from 'next/script'
function MyPage() {
return (
<div>
<Script
src="https://example.com/script.js"
strategy="afterInteractive"
onLoad={() => console.log('Script loaded')}
/>
</div>
)
}
三种加载策略详解
1. beforeInteractive
执行时机:在页面交互之前,HTML 解析完成后立即执行
// 关键脚本,必须在页面交互前加载
<Script
src="https://polyfill.io/v3/polyfill.min.js"
strategy="beforeInteractive"
onLoad={() => console.log('Polyfill loaded')}
/>
// 内联脚本
<Script
id="inline-script"
strategy="beforeInteractive"
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
`,
}}
/>
使用场景:
- Polyfills
- 关键的分析脚本
- 必须在页面交互前执行的脚本
特点:
- 阻塞页面渲染
- 确保脚本在用户交互前可用
- 影响 First Contentful Paint (FCP)
2. afterInteractive (默认)
执行时机:页面变为交互状态后执行
// 默认策略,页面交互后加载
<Script
src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
strategy="afterInteractive"
onLoad={() => {
gtag('config', 'GA_MEASUREMENT_ID')
}}
/>
// 社交媒体脚本
<Script
src="https://connect.facebook.net/en_US/sdk.js"
strategy="afterInteractive"
onLoad={() => {
FB.init({
appId: 'your-app-id',
version: 'v18.0'
})
}}
/>
使用场景:
- Google Analytics
- 社交媒体 SDK
- 用户交互相关的脚本
特点:
- 不阻塞页面渲染
- 在页面交互后加载
- 平衡性能和功能
3. lazyOnLoad
执行时机:页面完全加载后,浏览器空闲时执行
// 延迟加载,页面完全加载后执行
<Script
src="https://widget.example.com/widget.js"
strategy="lazyOnLoad"
onLoad={() => {
console.log('Widget loaded')
}}
/>
// 聊天插件
<Script
src="https://widget.intercom.io/widget.js"
strategy="lazyOnLoad"
onLoad={() => {
Intercom('boot', {
app_id: 'your-app-id'
})
}}
/>
使用场景:
- 聊天插件
- 非关键的小部件
- 可以延迟加载的功能
特点:
- 不阻塞页面渲染
- 在页面完全加载后执行
- 最佳性能,但功能可能延迟
实际应用场景
电商网站脚本优化
function EcommercePage() {
return (
<div>
{/* 关键脚本:支付安全 */}
<Script
src="https://js.stripe.com/v3/"
strategy="beforeInteractive"
onLoad={() => {
window.Stripe = Stripe('pk_test_...')
}}
/>
{/* 分析脚本:用户行为追踪 */}
<Script
src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
strategy="afterInteractive"
onLoad={() => {
gtag('config', 'GA_MEASUREMENT_ID')
}}
/>
{/* 聊天插件:延迟加载 */}
<Script
src="https://widget.intercom.io/widget.js"
strategy="lazyOnLoad"
onLoad={() => {
Intercom('boot', { app_id: 'your-app-id' })
}}
/>
</div>
)
}
博客网站脚本优化
function BlogPost({ post }) {
return (
<div>
{/* 代码高亮:页面交互后加载 */}
<Script
src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"
strategy="afterInteractive"
onLoad={() => {
Prism.highlightAll()
}}
/>
{/* 社交分享:延迟加载 */}
<Script
src="https://platform.twitter.com/widgets.js"
strategy="lazyOnLoad"
onLoad={() => {
twttr.widgets.load()
}}
/>
</div>
)
}
性能优化策略
1. 脚本优先级管理
function OptimizedPage() {
return (
<div>
{/* 关键脚本:最高优先级 */}
<Script src="/critical-script.js" strategy="beforeInteractive" priority />
{/* 重要脚本:页面交互后 */}
<Script src="/important-script.js" strategy="afterInteractive" />
{/* 非关键脚本:延迟加载 */}
<Script src="/non-critical-script.js" strategy="lazyOnLoad" />
</div>
)
}
2. 条件加载
function ConditionalScripts({ userType }) {
return (
<div>
{/* 根据用户类型条件加载 */}
{userType === 'premium' && (
<Script src="/premium-features.js" strategy="afterInteractive" />
)}
{/* 根据环境条件加载 */}
{process.env.NODE_ENV === 'production' && (
<Script
src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
strategy="afterInteractive"
/>
)}
</div>
)
}
3. 错误处理
function ScriptWithErrorHandling() {
return (
<Script
src="https://example.com/script.js"
strategy="afterInteractive"
onLoad={() => {
console.log('Script loaded successfully')
}}
onError={(e) => {
console.error('Script failed to load:', e)
// 降级处理
fallbackFunction()
}}
/>
)
}
性能监控
Core Web Vitals 影响
// 监控脚本加载对性能的影响
function PerformanceOptimizedScripts() {
return (
<div>
{/* 关键脚本:影响 FCP */}
<Script
src="/critical.js"
strategy="beforeInteractive"
onLoad={() => {
// 记录关键脚本加载时间
performance.mark('critical-script-loaded')
}}
/>
{/* 非关键脚本:不影响核心指标 */}
<Script
src="/analytics.js"
strategy="lazyOnLoad"
onLoad={() => {
// 记录非关键脚本加载时间
performance.mark('analytics-script-loaded')
}}
/>
</div>
)
}
最佳实践
1. 脚本分类
// 按重要性分类脚本
const scripts = {
critical: [
{ src: '/polyfill.js', strategy: 'beforeInteractive' },
{ src: '/security.js', strategy: 'beforeInteractive' },
],
important: [
{ src: '/analytics.js', strategy: 'afterInteractive' },
{ src: '/user-tracking.js', strategy: 'afterInteractive' },
],
optional: [
{ src: '/chat-widget.js', strategy: 'lazyOnLoad' },
{ src: '/social-share.js', strategy: 'lazyOnLoad' },
],
}
2. 动态加载
function DynamicScriptLoader({ scriptUrl, strategy = 'afterInteractive' }) {
const [loaded, setLoaded] = useState(false)
return (
<Script
src={scriptUrl}
strategy={strategy}
onLoad={() => setLoaded(true)}
onError={() => console.error('Script failed to load')}
/>
)
}
总结
Next.js 15 的 next/script 组件提供了三种加载策略:
beforeInteractive:
- 页面交互前执行
- 阻塞渲染,影响 FCP
- 适用于关键脚本和 Polyfills
afterInteractive (默认):
- 页面交互后执行
- 不阻塞渲染
- 适用于分析脚本和用户交互相关脚本
lazyOnLoad:
- 页面完全加载后执行
- 最佳性能
- 适用于非关键功能和小部件
最佳实践:
- 按重要性分类脚本
- 使用条件加载
- 监控性能影响
- 处理加载错误
- 合理选择加载策略