Membaca Pesan Errornya

Ketika kamu menulis side-effect import untuk file CSS di proyek TypeScript, misalnya di dalam layout.tsx pada proyek Next.js:

import './globals.css'

Kamu bisa jadi mendapati error berikut muncul di editor (VS Code) maupun di terminal saat build:

Cannot find module './globals.css' or its corresponding type declarations.
ts(2307)

Atau variasi yang lebih eksplisit:

Could not find a declaration file for module './globals.css'.
'./globals.css' implicitly has an 'any' type.

Arti singkat: TypeScript secara default hanya mengenali file-file yang berakhungan .ts, .tsx, .js, dan .jsx sebagai module yang bisa di-import. File .css tidak memiliki type declarations bawaan, sehingga TypeScript tidak tahu apa "bentuk" dari module tersebut — apakah ia mengekspor sesuatu, atau bahkan apakah ia valid sebagai module. Akibatnya, compiler menolak impor tersebut dan melempar error TS2307.

Kenapa error ini muncul? TypeScript dirancang sebagai typed superset dari JavaScript. Dunia TypeScript sangat ketat soal type safety: setiap import harus merujuk ke module yang memiliki type declaration, baik secara eksplisit (file .d.ts) maupun implisit (file .ts/.tsx). File CSS, SVG, JSON, dan lain-lain berada di luar dunia itu secara default. Side-effect import seperti import './globals.css' memang tidak mengharapkan nilai kembalian — ia hanya memberi tahu bundler untuk menyertakan file tersebut di output — tetapi TypeScript tetap memvalidasi setiap statement import terhadap type system-nya. Jadi, meskip4pun runtime (browser + bundler) tidak bermasalah, TypeScript tetap protes karena ia tidak punya informasi type untuk file tersebut.


Sumber Masalahnya

Berikut adalah root cause yang paling sering menyebabkan error ini, diurutkan dari yang paling frekuen:

  1. Tidak ada declaration file untuk file CSS. Ini adalah penyebab nomor satu. TypeScript mencari file .d.ts yang mendeklarasikan module *.css, tetapi tidak menemukannya di mana pun dalam proyek. Situasi ini hampir selalu terjadi pada proyek baru yang belum mengonfigurasi custom type declarations.

  2. File globals.d.ts atau css.d.ts ada, tetapi tidak ter-include oleh tsconfig.json. Kamu bisa saja sudah membuat declaration file yang benar, namun jika include atau files array di tsconfig.json tidak mencakup direktori tempat file .d.ts itu berada, TypeScript akan mengabaikannya sepenuhnya. Ini sering terjadi ketika declaration file diletakkan di subdirektori khusus (misalnya src/types/) yang berada di luar glob include.

  3. types atau typeRoots di tsconfig.json terlalu restriktif. Properti types di tsconfig.json membatasi package @types/* mana saja yang di-include. Jika kamu menambahkan properti types: ["node"] tanpa menyertakan "webpack-env" atau type custom yang relevan, TypeScript tidak akan memuat type declaration lain, termasuk yang kamu buat sendiri di typeRoots.

  4. Konflik antara beberapa tsconfig.json (monorepo / Next.js internal). Proyek Next.js menggunakan dua file tsconfig.json: satu untuk IDE (tsconfig.json) dan satu untuk build internal (tsconfig.build.json atau via plugin). Di monorepo dengan Turborepo atau Nx, bisa ada multiple tsconfig yang saling override. Declaration yang valid di satu config bisa tidak terbaca di config lain.

  5. Versi TypeScript lama yang tidak mendukung resolveJsonModule atau fitur module resolution terbaru. Meskipun ini bukan langsung tentang CSS, versi lama TypeScript (di bawah 4.0) kadang memiliki behavior module resolution yang berbeda, sehingga declaration pattern seperti declare module '*.css' tidak di-resolve dengan benar.

Cara Memastikan Penyebab Mana yang Kamu Alami

Langkah pertama adalah memeriksa apakah declaration file sudah ada. Jalankan:

find . -name "*.d.ts" -path "*/css*" -o -name "*.d.ts" -path "*/global*"

Jika tidak ada output, kamu mengalami penyebab #1. Jika file ada, periksa tsconfig.json:

cat tsconfig.json | grep -E '"include"|"types"|"typeRoots"|"files"'

Jika include tidak mencakup direktori declaration file, itu penyebab #2. Jika types array eksplisit ada dan terlalu sempit, itu penyebab #3. Untuk monorepo, cek apakah ada lebih dari satu tsconfig.json:

find . -name "tsconfig*.json"

Cara Memperbaiki

Langkah 1: Buat File Declaration untuk CSS

Buat file baru bernama globals.d.ts (atau css.d.ts) di root proyek atau di direktori yang sama dengan tsconfig.json:

touch globals.d.ts

Isi file tersebut dengan:

// globals.d.ts
declare module '*.css' {
  const content: { [className: string]: string };
  export default content;
}

Atau, jika kamu hanya menggunakan side-effect import dan tidak peduli dengan return value:

// globals.d.ts
declare module '*.css';

Output yang diharapkan: Setelah file ini disimpan, error di editor harus langsung hilang karena TypeScript sekarang bisa menemukan declaration untuk module *.css.

Langkah 2: Pastikan tsconfig.json Meng-include File Declaration

Buka tsconfig.json dan verifikasi bahwa glob include mencakup file .d.ts yang baru dibuat:

{
  "compilerOptions": {
    // ... konfigurasi lainnya
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    "**/*.d.ts"
  ],
  "exclude": ["node_modules"]
}

Pattern **/*.d.ts memastikan semua declaration file di seluruh direktori proyek ter-include. Jika sebelumnya include tidak ada, TypeScript secara default include semua file .ts dan .tsx, tapi tidak selalu include .d.ts — jadi eksplisit menambahkannya adalah langkah yang aman.

Langkah 3: Restart TypeScript Server di Editor

Di VS Code, buka Command Palette (Ctrl+Shift+P / Cmd+Shift+P) dan jalankan:

TypeScript: Restart TS Server

Ini penting. VS Code meng-cache type information. Meskipun file .d.ts sudah benar, editor bisa tetap menampilkan error lama dari cache.

Langkah 4: Verifikasi dengan Build

Jalankan build untuk memastikan error benar-benar resolved:

npx tsc --noEmit

Output yang diharapkan:

# Tidak ada output error terkait CSS import
# Atau hanya output sukses tanpa error TS2307

Jika masih ada error, perhatikan path file yang error — itu petunjuk bahwa declaration file tidak ter-cover oleh include config.

Langkah 5 (Root Cause #3): Perbaiki types Array yang Terlalu Restriktif

Jika tsconfig.json memiliki properti types yang eksplisit:

{
  "compilerOptions": {
    "types": ["node"]
  }
}

Kamu perlu menghapus properti ini atau menambahkan path ke custom declaration. Opsi yang lebih bersih adalah menghapus properti types sepenuhnya, karena ketika types tidak ditentukan, TypeScript akan otomatis meng-include semua @types/* packages dan custom .d.ts files yang ditemukan. Setelah menghapus, jalankan kembali npx tsc --noEmit untuk verifikasi.


Supaya Tidak Terulang

Solusi di atas sudah bersifat permanen, namun ada beberapa langkah konfigurasi tambahan yang memastikan error ini tidak pernah kembali di masa depan, bahkan ketika kamu menambahkan file CSS baru atau menambah anggota tim yang mungkin tidak tahu tentang konfigurasi ini.

Konfigurasi Declaration yang Komprehensif

Daripada hanya mendeklarasikan *.css, buat declaration file yang menangani semua jenis non-TS/JS module yang lazim di proyek web modern:

// src/types/assets.d.ts
declare module '*.css' {
  const content: Record<string, string>;
  export default content;
}

declare module '*.scss' {
  const content: Record<string, string>;
  export default content;
}

declare module '*.svg' {
  const content: string;
  export default content;
}

declare module '*.png' {
  const content: string;
  export default content;
}

declare module '*.jpg' {
  const content: string;
  export default content;
}

declare module '*.jpeg' {
  const content: string;
  export default content;
}

declare module '*.gif' {
  const content: string;
  export default content;
}

declare module '*.woff' {
  const content: string;
  export default content;
}

declare module '*.woff2' {
  const content: string;
  export default content;
}

Dengan satu file ini, semua impor asset non-code di masa depan tidak akan memicu error yang sama.

Best Practice: Organisasi Type Declarations

Buat direktori khusus untuk type declarations dan pastikan tsconfig.json merujuk ke sana:

mkdir -p src/types

Pindahkan semua file .d.ts ke direktori ini. Kemudian di tsconfig.json:

{
  "compilerOptions": {
    "typeRoots": [
      "./node_modules/@types",
      "./src/types"
    ]
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    "**/*.d.ts"
  ]
}

Properti typeRoots memberi tahu TypeScript untuk mencari type declaration di dua lokasi: @types packages dari npm dan folder custom ./src/types. Ini adalah konfigurasi yang paling robust karena eksplisit dan tidak bergantung pada convention.

Perbedaan OS

Ada satu nuansa penting terkait sistem operasi. Di Windows, path separator adalah backslash (\), dan terkadang TypeScript di Windows tidak meresolve glob pattern dengan benar jika file .d.ts berada di nested directory dengan path yang menggunakan forward slash. Solusinya: selalu gunakan forward slash di tsconfig.json (TypeScript menangani konversi secara internal).

Di macOS dan Linux, file system bersifat case-sensitive (tergantung konfigurasi). Jika kamu menulis import './Globals.css' tetapi file aslinya globals.css, TypeScript di OS case-sensitive akan melempar error module not found yang berbeda (TS2307 juga, tapi karena file memang tidak ditemukan, bukan karena deklarasi type). Di Windows (case-insensitive), import tersebut bisa jadi "work" di editor tapi gagal di CI/CD yang berjalan di Linux. Pastikan case pada import statement persis sama dengan nama file.

Konfigurasi Next.js Spesifik

Jika kamu menggunakan Next.js 13+ dengan App Router, Next.js sebenarnya sudah menyertakan next-env.d.ts yang meng-reference @next/types. Namun, ini tidak otomatis mendeklarasikan module CSS. Kamu tetap perlu membuat declaration file manual seperti di atas. Yang bisa kamu manfaatkan dari Next.js adalah memastikan next-env.d.ts ter-include:

{
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    "**/*.d.ts",
    ".next/types/**/*.ts"
  ]
}

Baris .next/types/**/*.ts penting di Next.js 14+ karena framework ini generate type definitions dinamis di direktori .next/types/ untuk route handler dan metadata types.


Kalau Cara di Atas Gagal

Kalau Solusi