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:
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.
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.tsIsi 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 ServerIni 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 --noEmitKeluaran yang tampak:
# Tidak ada output error terkait CSS import
# Atau hanya output sukses tanpa error TS2307Jika 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/typesPindahkan 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/.cachejika ada. - Hapus file
tsconfig.tsbuildinfodi root project. - Jalankan
tsc --noEmitdari 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.