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.

Makna pesan ini: 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 bisa 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 akar masalah yang paling sering memicu 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.

Cek penyebab yang berlaku di kasusmu

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';

Yang akan kamu lihat: 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

Keluaran yang tampak:

# 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.

Kebiasaan yang baik: 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 di Atas Masih Tidak Work? Jangan panik. Ada beberapa skenario edge case yang sering terlewat.

1. Cache TypeScript yang Stale

TypeScript memiliki internal cache yang bisa menjadi stale setelah kamu menambah atau mengubah file .d.ts. Restart TS server saja kadang tidak cukup. Coba langkah berikut:

  • Hapus direktori node_modules/.cache jika ada.
  • Hapus file tsconfig.tsbuildinfo di root project.
  • Jalankan tsc --noEmit dari terminal untuk memvalidasi apakah error masih muncul di luar editor.
  • Di VS Code, jalankan TypeScript: Restart TS Server dari Command Palette, atau tutup dan buka kembali workspace.

2. typeRoots yang Meng-override Default

Jika kamu mendefinisikan typeRoots di tsconfig.json, TypeScript hanya akan mencari type declarations di path yang kamu sebutkan — dia tidak lagi meng-include node_modules/@types secara default. Ini artinya declaration file custom kamu bisa saja tidak ditemukan.

Solusinya: tambahkan path tempat file .d.ts kamu berada ke dalam typeRoots:

{
  "compilerOptions": {
    "typeRoots": [
      "./node_modules/@types",
      "./types"
    ]
  }
}

Dengan konfigurasi ini, TypeScript akan mencari di kedua lokasi: package @types dan folder types lokal kamu.

3. strict Mode dan verbatimModuleSyntax

Di TypeScript 5.0+, opsi verbatimModuleSyntax memaksa kamu untuk menggunakan keyword type secara eksplisit pada import type-only. Meskipun ini tidak langsung berhubungan dengan CSS import, kombinasi dengan isolatedModules: true (yang di-set otomatis oleh Next.js) bisa menyebabkan TypeScript lebih ketat dalam mengevaluasi module resolution. Pastikan declaration file kamu menggunakan declare module (bukan export default), karena isolatedModules tidak kompatibel dengan ambient re-export.

4. Monorepo dan Project References

Jika project kamu menggunakan monorepo (Turborepo, Nx, dll) dengan composite: true dan TypeScript Project References, setiap sub-project memiliki tsconfig sendiri. Declaration file di root workspace tidak otomatis tersedia di sub-project. Kamu harus membuat file .d.ts terpisah di masing-masing package yang melakukan CSS import, atau menggunakan shared tsconfig sebagai base dengan extends:

{
  "extends": "../../tsconfig.base.json",
  "include": ["src", "../../types"]
}

Ringkasan

Penyebab Solusi
Tidak ada declaration file Buat *.d.ts dengan declare module '*.css'
File .d.ts tidak ter-include Tambahkan **/*.d.ts ke include di tsconfig.json
Path menggunakan backslash (Windows) Gunakan forward slash
Case mismatch pada import Pastikan case persis sama dengan nama file
typeRoots meng-override default Tambahkan path folder types ke typeRoots
Cache stale Hapus tsconfig.tsbuildinfo, restart TS server

Error ini memang frustasi karena terasa seperti seharusnya "just work" — CSS import adalah hal yang paling dasar. Tapi sekali kamu paham bahwa TypeScript memperlakukan setiap import sebagai module yang perlu di-resolve typenya, logika di balik solusi ini menjadi jelas.