내 컴퓨터에서만 되는 코드

개발자들 사이에 오래된 농담이 하나 있다. "내 컴퓨터에서는 되는데요"라는 말에 "그럼 네 컴퓨터를 배포하자"라고 받아치는 농담이다. 컨테이너를 설명하는 밈으로 자주 돌아다닌다. 웃고 넘기기에는 뼈가 있는 농담이다.
같은 코드를 돌렸는데 결과가 다르다면 코드 말고 다른 무언가가 달랐다는 뜻이다. 프로그램은 코드만 입력으로 받지 않는다. 파일 시스템의 규칙이나 시간대, 설치된 런타임 버전, 환경 변수 같은 것들이 코드에 적히지 않은 채로 함께 들어간다. 내 컴퓨터에서는 이 값들이 늘 같아서 그런 입력이 있다는 사실 자체를 잊고 지낸다.
이번 포스팅에서는 이런 숨은 입력 중에서 프론트엔드 개발자가 자주 밟는 두 가지를 예제로 살펴보려고 한다.
파일 이름의 대소문자
macOS 의 기본 파일 시스템인 APFS 는 대소문자를 구분하지 않는다. Button.tsx 와 button.tsx 를 같은 파일로 본다. 반면 대부분의 리눅스 서버가 쓰는 ext4 는 둘을 다른 파일로 본다.
그래서 이런 코드는 맥에서 잘 돌아간다.
// 실제 파일 components/Button.tsx
import Button from "./components/button";로컬 개발 서버에서도 로컬 빌드에서도 문제가 없다. 그런데 같은 코드를 리눅스 CI 나 배포 서버에서 빌드하면 ./components/button 이라는 모듈을 찾을 수 없다는 에러가 난다. 서버 입장에서 button.tsx 라는 파일은 정말로 없다.
이걸 고치려고 파일 이름을 바꿀 때 한 번 더 걸린다. 맥에서 button.tsx 를 Button.tsx 로 바꿔도 git status 에 아무 변화가 뜨지 않을 때가 있다. git 은 대소문자를 구분하지 않는 파일 시스템에서 저장소를 만들 때 core.ignorecase 설정을 true 로 켠다. 이 설정이 켜져 있으면 대소문자만 바뀐 이름 변경은 변경으로 잡히지 않는다. 이름을 바꿨다고 생각하고 푸시해도 원격 저장소에는 옛 이름이 그대로 남는다.
이럴 때는 git mv 로 이름을 바꿔야 변경이 기록된다.
git mv components/button.tsx components/Button.tsxTypeScript 를 쓴다면 tsconfig 의 forceConsistentCasingInFileNames 옵션도 도움이 된다. import 경로와 디스크에 있는 파일 이름의 대소문자가 다르면 내 컴퓨터에서도 컴파일 에러를 낸다. 서버에서 처음 터질 문제를 내 컴퓨터로 미리 끌어오는 옵션이다.
날짜 문자열의 시간대
두 번째는 시간대다. 아래 코드는 서울에 있는 개발자의 컴퓨터에서 24를 출력한다.
const date = new Date("2026-10-24");
console.log(date.getDate());같은 코드를 미국 서부 시간대의 컴퓨터에서 돌리면 23이 나온다. 자바스크립트는 2026-10-24 처럼 날짜만 있는 문자열을 UTC 자정으로 읽는다. getDate() 는 그 시각을 실행 중인 컴퓨터의 시간대로 바꾼 다음 날짜를 꺼낸다. 서울은 UTC 보다 9시간 빠르니 같은 날 오전 9시가 된다. 미국 서부는 10월이면 UTC 보다 7시간 느려서 전날 오후 5시가 된다.
서버는 보통 UTC 로 돌아가니 서버에서는 이 버그가 안 보일 수도 있다. 이 코드가 브라우저에서 돈다면 미국에 있는 사용자만 하루 전 날짜를 보게 된다. 서울에서 개발하고 서울에서 테스트하는 동안에는 한 번도 재현되지 않는 버그다.
헷갈리는 점이 하나 더 있다. 문자열에 시각이 붙어 있는지에 따라 읽는 기준이 달라진다.
| 문자열 | 읽는 기준 |
|---|---|
| 2026-10-24 | UTC |
| 2026-10-24T00:00 | 실행하는 컴퓨터의 시간대 |
| 2026-10-24T00:00+09:00 | 문자열에 적힌 오프셋 |
T00:00 몇 글자 때문에 기준이 바뀌니 처음 보면 꽤 당황스럽다. 마지막 줄처럼 오프셋을 직접 적어 두면 어느 컴퓨터에서 읽든 같은 시각이 된다.
내 컴퓨터에서 이 버그를 보고 싶다면 TZ 환경 변수로 시간대를 바꿔서 돌려 보면 된다.
TZ=America/Los_Angeles node date.js숨은 입력을 드러내기
두 예제를 고친 방법은 모양이 비슷하다. 대소문자 문제는 컴파일러 옵션 하나로 내 컴퓨터에서 미리 잡았다. 시간대 문제를 드러낸 건 문자열에 붙인 오프셋과 테스트 명령 앞의 TZ 한 줄이었다. 컴퓨터가 몰래 채워 주던 값을 코드나 설정에 보이게 적은 것이다.
런타임 버전도 같은 식으로 다룰 수 있다. .nvmrc 파일이나 package.json 의 engines 필드에 Node 버전을 적어 두면 npm 이 다른 버전에서 설치할 때 경고를 띄운다. 패키지 버전은 lock 파일이 붙잡아 둔다. 컨테이너는 이 생각을 운영체제까지 밀고 간 도구다. 운영체제와 런타임을 이미지 하나에 담아서 내 컴퓨터와 서버가 같은 입력을 받게 만든다. 앞의 농담이 결국 진담이 된 셈이다.
다음에 내 컴퓨터에서만 되는 코드를 만나면 코드를 들여다보기 전에 두 환경의 차이부터 적어 보자. 운영체제와 시간대, Node 버전, 환경 변수 네 줄만 나란히 적어도 답이 그 안에 있는 경우가 꽤 많다.