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:
Tidak ada declaration file untuk file CSS. Ini adalah penyebab nomor satu. TypeScript mencari file
.d.tsyang 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.File
globals.d.tsataucss.d.tsada, tetapi tidak ter-include olehtsconfig.json. Kamu bisa saja sudah membuat declaration file yang benar, namun jikaincludeataufilesarray ditsconfig.jsontidak mencakup direktori tempat file.d.tsitu berada, TypeScript akan mengabaikannya sepenuhnya. Ini sering terjadi ketika declaration file diletakkan di subdirektori khusus (misalnyasrc/types/) yang berada di luar globinclude.typesatautypeRootsditsconfig.jsonterlalu restriktif. Propertitypesditsconfig.jsonmembatasi package@types/*mana saja yang di-include. Jika kamu menambahkan propertitypes: ["node"]tanpa menyertakan"webpack-env"atau type custom yang relevan, TypeScript tidak akan memuat type declaration lain, termasuk yang kamu buat sendiri ditypeRoots.Konflik antara beberapa
tsconfig.json(monorepo / Next.js internal). Proyek Next.js menggunakan dua filetsconfig.json: satu untuk IDE (tsconfig.json) dan satu untuk build internal (tsconfig.build.jsonatau 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.Versi TypeScript lama yang tidak mendukung
resolveJsonModuleatau 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 sepertideclare 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.