首页 / Next.js 16 入门教程 / 第三方库集成

Next.js 16 入门教程

第三方库集成

本教程共 42 篇 · 第 36 篇 · 更新于 2026-07-30 · 约 6 分钟阅读

Next.jsNext.js 16 入门教程第三方库打包优化transpilePackages@next/third-parties

36. 第三方库集成

本节目标:掌握在 Next.js 中正确集成第三方库的方法,学会处理客户端/服务端兼容问题和打包优化。

客户端/服务端兼容性

Next.js 的 App Router 混合了 Server Components 和 Client Components。很多第三方库只支持浏览器环境,直接用在服务端会报错。

判断库是否兼容服务端

  • 如果库使用了 windowdocument 等浏览器 API,它只能在客户端运行
  • 如果库涉及 DOM 操作,它只能在客户端运行
  • 纯计算类库(如 lodashdate-fns)通常两端都能用

解决方案

方案一:标记为客户端组件

'use client'

import { SomeLibrary } from 'some-browser-only-library'

export function MyComponent() {
  // 这个组件及其子树都会在客户端执行
  return <SomeLibrary />
}

方案二:动态导入并关闭 SSR

import dynamic from 'next/dynamic'

const BrowserOnlyComponent = dynamic(
  () => import('@/components/BrowserOnlyComponent'),
  { ssr: false }
)

export default function Page() {
  return <BrowserOnlyComponent />
}

方案三:用 useEffect 延迟渲染

'use client'

import { useState, useEffect } from 'react'

export function ClientOnly({ children }) {
  const [mounted, setMounted] = useState(false)

  useEffect(() => {
    setMounted(true)
  }, [])

  return mounted ? <>{children}</> : null
}

transpilePackages

默认情况下,Next.js 不会编译 node_modules 中的代码。如果某个依赖包发布的是 TypeScript 源码或现代语法,需要手动加入编译列表:

// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  transpilePackages: ['@scope/ui', 'some-uncompiled-package'],
}

export default nextConfig

这个选项替代了以前的 next-transpile-modules 包。

什么时候需要 transpilePackages

  1. 依赖包发布的是 TypeScript 源码
  2. monorepo 中的共享包(Pages Router + Webpack 场景)
  3. Pages Router 下需要把 node_modules 依赖打包进路由

注意:包不能同时出现在 transpilePackagesserverExternalPackages 中,否则构建会报错。

@next/third-parties

Next.js 提供了 @next/third-parties 包,优化了常见第三方服务的加载方式。

Google Tag Manager

import { GoogleTagManager } from '@next/third-parties/google'

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <GoogleTagManager gtmId="GTM-XYZ" />
      <body>{children}</body>
    </html>
  )
}

Google Analytics

import { GoogleAnalytics } from '@next/third-parties/google'

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
      <GoogleAnalytics gaId="G-XYZ" />
    </html>
  )
}

Google Maps

import { GoogleMapsEmbed } from '@next/third-parties/google'

export default function Page() {
  return (
    <GoogleMapsEmbed
      apiKey="XYZ"
      height={200}
      width="100%"
      mode="place"
      q="Brooklyn+Bridge,New+York,NY"
    />
  )
}

YouTube 嵌入

import { YouTubeEmbed } from '@next/third-parties/google'

export default function Page() {
  return <YouTubeEmbed videoid="ogfYd705cRs" height={400} params="controls=0" />
}

注意@next/third-parties 目前还是实验性库,建议安装时用 @latestcanary 标签。

常见库的集成方案

状态管理

Zustand:轻量级状态管理,天然支持 React Server Components。

'use client'

import { create } from 'zustand'

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
}))

Redux:需要包裹 <Provider>,且 Provider 必须是客户端组件。

表单

React Hook Form + Zod

'use client'

import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'

const schema = z.object({
  email: z.string().email(),
})

export function MyForm() {
  const { register, handleSubmit } = useForm({
    resolver: zodResolver(schema),
  })

  return <form onSubmit={handleSubmit(onSubmit)}>{/* ... */}</form>
}

动画

Framer Motion

'use client'

import { motion } from 'framer-motion'

export function AnimatedBox() {
  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      transition={{ duration: 0.5 }}
    />
  )
}

日期处理

date-fns:纯函数库,两端都能用。

import { format } from 'date-fns'

export default async function Page() {
  const today = format(new Date(), 'yyyy-MM-dd')
  return <p>今天是 {today}</p>
}

打包优化建议

  1. 按需导入import { Button } from 'antd'import antd from 'antd'
  2. 动态导入:非首屏组件用 dynamic() 懒加载
  3. tree-shaking:确保库支持 ES Module,能被正确摇树
  4. 监控包大小:用 @next/bundle-analyzer 分析打包产物(安装与配置方法见 §27)

集成第三方库时,最关键的是理解它能不能在服务端运行。不确定的时候,先用 'use client' 标记,再根据实际需求调整。