如何优化第三方脚本的加载?next/script 组件的 strategy 属性(beforeInteractive, afterInteractive, la

38 阅读3分钟

如何优化第三方脚本的加载?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

  • 页面完全加载后执行
  • 最佳性能
  • 适用于非关键功能和小部件

最佳实践

  • 按重要性分类脚本
  • 使用条件加载
  • 监控性能影响
  • 处理加载错误
  • 合理选择加载策略