빠른 비교
path.join("src", "utils", "helper.js");
path.resolve("src", "utils", "helper.js");
const __dirname = dirname(fileURLToPath(import.meta.url));
const filePath = join(__dirname, "data.json");경로와 URL
먼저 구분해 둘 기본형은 아래입니다.
- 세그먼트 조립:
path.join(...) - 절대 위치 계산:
path.resolve(...) - URL -> 경로:
fileURLToPath(...) - 경로 -> URL:
pathToFileURL(...) - ESM 기준점:
import.meta.url
join과 resolve
join은 경로 조각을 붙이는 쪽이고, resolve는 최종적으로 절대 경로를 만들고 싶을 때 더 자주 씁니다.
path.join("src", "utils", "helper.js");
path.resolve("src", "utils", "helper.js");실전에서는 파일 시스템 API에 넘길 경로라면 resolve 쪽이 더 안전한 경우가 많습니다. 반면 URL 경로나 내부 상대 세그먼트 조립은 join이 더 자연스러울 수 있습니다.
path.join("/app", "/tmp", "a.txt"); // 세그먼트 연결
path.resolve("/app", "/tmp", "a.txt"); // 절대 경로 기준 재계산즉 join은 "붙인다", resolve는 "최종 위치를 계산한다"로 읽는 편이 빠릅니다.
cwd 기준인지 현재 파일 기준인지 먼저 나눈다
실무에서 경로 버그는 join과 resolve 차이보다도 "이 경로가 어디를 기준으로 계산되느냐"에서 더 자주 납니다.
사용자 실행 위치 기준이면 process.cwd(), 현재 모듈 기준이면 import.meta.url 또는 __dirname 패턴을 먼저 떠올리는 편이 맞습니다.
ESM 기준점
ESM에서는 import.meta.url부터 시작한다
ESM에서 많이 다시 찾는 패턴은 이겁니다.
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
const __dirname = dirname(fileURLToPath(import.meta.url));
const filePath = join(__dirname, "data.json");핵심은 import.meta.url이 파일 경로가 아니라 URL이라는 점입니다.
즉:
- path 함수에 바로 넣지 않는다
- 먼저
fileURLToPath()로 변환한다
변환 경계
경로와 URL은 서로 바꿔야 할 때가 있다
- 파일 시스템 API는 보통 경로를 기대
- Worker, 동적 import, 일부 Web API 흐름은 URL이 더 자연스러움
그래서 fileURLToPath()와 pathToFileURL()은 "한 번쯤 필요한 희귀 함수"가 아니라 ESM 환경에서 계속 다시 찾는 변환 카드입니다.
const workerUrl = pathToFileURL("/app/worker.js");언제 무엇을 기준으로 쓸까
체크포인트
- 상대 세그먼트 조립:
join - 절대 경로 필요:
resolve - ESM 파일 위치 기준:
fileURLToPath(import.meta.url) - 경로/URL 경계는 명시적으로 변환
cwd기준인지 현재 파일 기준인지 먼저 확인
잘못된 예시는 보통 이쪽입니다.
const filePath = path.join(import.meta.url, "data.json");import.meta.url은 경로가 아니라 URL이므로 바로 path.join()에 넣으면 안 됩니다.
주의할 점
경로와 URL은 비슷해 보여도 타입과 의미가 다릅니다. 문자열로 대충 이어 붙이면 로컬에서는 우연히 돌아가도, 공백·인코딩·운영체제 차이에서 바로 깨지기 쉽습니다. 특히 ESM 파일 기준 경로는 항상 URL → path 변환을 먼저 떠올리는 편이 안전합니다.
참고 링크
2 sources