Fix Error Native Binding @tailwindcss/oxide di pnpm Workspace dengan Next.js

4 menit baca.
Tags: nextjsjavascript

Kamu pakai Tailwind CSS v4 di pnpm workspace dengan Next.js, terus muncul error ini:

Error: Cannot find native binding.
Caused by: Error: Cannot find module '@tailwindcss/oxide-darwin-arm64'
Caused by: Error: Cannot find module './tailwindcss-oxide.darwin-arm64.node'

Ini penyebabnya dan cara fixnya.

Kenapa @tailwindcss/oxide tidak bisa menemukan native binding-nya

Tailwind CSS v4 pakai @tailwindcss/oxide, engine berbasis Rust, untuk memproses CSS. Package ini dikirim sebagai wrapper yang platform-agnostic, dengan binary aslinya ada di optional package seperti @tailwindcss/oxide-darwin-arm64, @tailwindcss/oxide-linux-x64-gnu, dan sebagainya.

Saat runtime, oxide/index.js mencoba load binary native dengan dua cara:

// coba relative file dulu
require('./tailwindcss-oxide.darwin-arm64.node')

// fallback ke optional package
require('@tailwindcss/oxide-darwin-arm64')

Di setup npm atau yarn biasa ini jalan normal. Di pnpm workspace dengan Next.js Turbopack, keduanya gagal.

Masalahnya: pnpm + Turbopack

pnpm pakai isolated linker: setiap package dapat node_modules sendiri dengan symlink ke dependensinya. Jadi @tailwindcss/oxide-darwin-arm64 sudah di-symlink di bawah node_modules milik oxide, dan kalau kamu cd ke direktori package oxide lalu jalankan require('@tailwindcss/oxide-darwin-arm64'), resolusinya berjalan dengan benar.

Masalahnya ada di Turbopack. Turbopack mengkompilasi @tailwindcss/postcss dan @tailwindcss/oxide ke dalam worker bundle tersendiri di .next/dev/build/postcss.js, lalu menjalankannya di isolated worker context. Worker tersebut tidak mengikuti symlink chain milik pnpm dengan cara yang sama, sehingga kedua require path gagal, baik package require maupun relative file require.

Cara fix-nya

Step 1: pastikan optional dep-nya benar-benar terinstall. pnpm kadang skip. Jalankan dengan --force:

pnpm install --force

Step 2: copy binary .node native langsung ke direktori package oxide. Ini fix utamanya. Relative require ./tailwindcss-oxide.darwin-arm64.node akan jalan tanpa peduli module resolution context-nya dari mana.

cp node_modules/.pnpm/@tailwindcss+oxide-darwin-arm64@4.3.0/node_modules/@tailwindcss/oxide-darwin-arm64/tailwindcss-oxide.darwin-arm64.node \
   node_modules/.pnpm/@tailwindcss+oxide@4.3.0/node_modules/@tailwindcss/oxide/

Sesuaikan nomor versi (4.3.0) dengan yang kamu punya.

Step 3: hapus cache Next.js. Turbopack men-cache state yang gagal, jadi meskipun sudah difix, error lama akan muncul lagi sampai cache-nya dihapus:

rm -rf apps/web/.next

Restart dev dan selesai.

Biar fix-nya bertahan setelah reinstall

Binary yang sudah dicopy akan hilang setiap kali pnpm install. Tambahkan postinstall script di root package.json:

{
  "scripts": {
    "postinstall": "node scripts/fix-oxide-binding.mjs"
  }
}
// scripts/fix-oxide-binding.mjs
import { copyFileSync, existsSync, readdirSync } from 'node:fs'
import { join } from 'node:path'

const targets = {
  'darwin-arm64': 'darwin-arm64',
  'darwin-x64': 'darwin-x64',
  'linux-x64': 'linux-x64-gnu',
  'linux-arm64': 'linux-arm64-gnu',
}

const target = targets[`${process.platform}-${process.arch}`]
const store = 'node_modules/.pnpm'
if (!target || !existsSync(store)) process.exit(0)

const file = `tailwindcss-oxide.${target}.node`

for (const dir of readdirSync(store)) {
  const match = dir.match(/^@tailwindcss\+oxide@([^_]+)/)
  if (!match) continue
  const version = match[1]
  const src = join(store, `@tailwindcss+oxide-${target}@${version}`, 'node_modules/@tailwindcss', `oxide-${target}`, file)
  const dest = join(store, dir, 'node_modules/@tailwindcss/oxide', file)
  if (existsSync(src) && !existsSync(dest)) {
    copyFileSync(src, dest)
    console.log(`Copied ${file} into @tailwindcss/oxide@${version}`)
  }
}

Script ini loop ke semua versi oxide yang terinstall, jadi lockfile dengan dua versi Tailwind tidak bikin install gagal. Platform yang ditangani: darwin-arm64, darwin-x64, linux-x64, dan linux-arm64. Tambahkan entry ke targets kalau butuh lebih.

TL;DR

pnpm mengisolasi package di balik symlink. Worker PostCSS milik Turbopack tidak bisa resolve optional native dep melalui symlink tersebut. Copy binary .node langsung ke direktori oxide membuat relative require berjalan, dan hapus .next untuk membuang cache kegagalan. Tambahkan postinstall script biar fix-nya otomatis reapply setiap install.