Cách khắc phục lỗi Node.js [ERR_MODULE_NOT_FOUND]: Vấn đề thiếu phần mở rộng tệp

beginner💚 Node.js2026-07-24| Node.js (v12.17.0+, v14.0.0+), Linux, macOS, Windows, ES Modules (type: module)

Error Message

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '...' imported from ...
#nodejs#es-modules#javascript#backend

Thông báo lỗiNếu gần đây bạn đã chuyển một dự án sang ES Modules, có thể bạn đã thấy một loạt thông báo lỗi dài này trong terminal của mình:

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/path/to/project/utils' imported from /path/to/project/index.js
    at new NodeError (node:internal/errors:371:5)
    at finalizeResolution (node:internal/modules/esm/resolve:1018:11)
    at moduleResolve (node:internal/modules/esm/resolve:983:10)
    ... { 
  code: 'ERR_MODULE_NOT_FOUND'
}

Tại sao lỗi này xảy raViệc chuyển đổi từ CommonJS (require) sang ES Modules (import) đã thay đổi quy tắc cuộc chơi. Trong thời kỳ CommonJS trước đây, Node.js rất linh hoạt—có lẽ là quá mức. Bạn có thể viết const utils = require('./utils') và Node sẽ tự động tìm kiếm utils.js, utils.json, hoặc thậm chí là một tệp index.js bên trong một thư mục.

ES Modules tuân theo một đặc tả nghiêm ngặt hơn, tương thích với trình duyệt. Chúng yêu cầu các định danh đầy đủ (full specifiers). Node.js không còn tự đoán phần mở rộng tệp mà bạn muốn nữa. Nếu tệp là utils.js, mã của bạn phải ghi rõ utils.js. Một dấu chấm và hai chữ cái bị thiếu này chính là nguyên nhân của khoảng 90% các vấn đề đau đầu khi di chuyển sang ESM.

Cách khắc phục tức thì: Sử dụng phần mở rộng rõ ràngĐể xóa lỗi này, hãy thêm phần mở rộng .js vào tất cả các đường dẫn import tương đối. Không quan trọng bạn đang import một tệp JavaScript tiêu chuẩn hay một tài sản đã biên dịch; phần mở rộng là bắt buộc.

Cách làm sai```

// index.js import { helper } from './utils'; // Gây ra lỗi ERR_MODULE_NOT_FOUND


### Cách làm đúng```
// index.js
import { helper } from './utils.js'; // Hoạt động hoàn hảo

Lưu ý nhanh cho người dùng TypeScript: Điều này nghe có vẻ sai, nhưng bạn phải sử dụng phần mở rộng .js trong các câu lệnh import ngay cả khi tệp thực tế trên ổ đĩa của bạn là utils.ts. Trình biên dịch TypeScript (TSC) yêu cầu hành vi này khi hướng tới ESM, vì nó không thay đổi đường dẫn import trong quá trình biên dịch.

Sử dụng Flag "Sửa lỗi nhanh"Việc cập nhật một cơ sở mã khổng lồ với hơn 500 tệp không phải lúc nào cũng khả thi trong một buổi chiều. Nếu bạn đang vội, bạn có thể buộc Node.js hoạt động giống như CommonJS bằng cách sử dụng một flag thử nghiệm. Hãy sử dụng cách này một cách tiết kiệm, vì nó chỉ là giải pháp tạm thời chứ không phải là cách giải quyết triệt để.

node --experimental-specifier-resolution=node index.js

Flag này khôi phục thuật toán phân giải cũ, cho phép bạn bỏ qua phần mở rộng và index của thư mục. Chỉ cần lưu ý rằng flag này đã bị loại bỏ trong các phiên bản Node.js mới hơn (như Node 19+) để ưu tiên cho các loader tùy chỉnh.

Xử lý Import thư mụcTrước đây, bạn có thể trỏ đến một thư mục và Node sẽ tìm thấy tệp index.js bên trong. ESM không làm điều đó. Bạn phải chỉ định rõ ràng.

Kiểu CommonJS (Thất bại)```

import { api } from './services';


### Kiểu ESM (Chính xác)```
import { api } from './services/index.js';

Cấu hình VS Code để tự động hóaĐừng gõ phần mở rộng một cách thủ công mọi lúc. Bạn có thể tích hợp yêu cầu này vào logic tự động import của trình soạn thảo. Mở tệp .vscode/settings.json của bạn và thêm các dòng sau:

{
  "javascript.preferences.importModuleSpecifierEnding": "js",
  "typescript.preferences.importModuleSpecifierEnding": "js"
}

Giờ đây, khi bạn nhấn 'Enter' để tự động import một hàm, VS Code sẽ tự động thêm .js cho bạn.

Kiểm tra lại công việcTrước khi đẩy mã của bạn lên, hãy xem qua danh sách kiểm tra này:

  • Khởi chạy ứng dụng với node index.js. Nếu nó bắt đầu mà không có stack trace, bạn đã thành công.- Quét các import của bạn để tìm các đường dẫn tương đối bắt đầu bằng ./ hoặc ../. Mỗi cái đều cần một phần mở rộng.- Bỏ qua node_modules của bạn. Các import như import express from 'express' không cần phần mở rộng vì Node phân giải các gói khác với các tệp cục bộ.## Tự động ngăn ngừa với ESLintCách tốt nhất để ngăn lỗi này là biến nó thành một lỗi trong quá trình build. Sử dụng eslint-plugin-import để bắt các phần mở rộng bị thiếu trong quá trình phát triển. Thêm quy tắc này vào cấu hình của bạn:
"rules": {
  "import/extensions": ["error", "always", { "js": "always", "mjs": "always" }]
}

Khi quy tắc này hoạt động, trình soạn thảo của bạn sẽ gạch chân đỏ bất kỳ import nào không đầy đủ, ngăn chặn mã lỗi bị đẩy lên kho lưu trữ của bạn.

Related Error Notes