| 서론
안녕하세요, 팡일입니다.
프로젝트를 진행하면서 서버 데이터 호출 로직이 점점 복잡해져, API 모듈 구조를 정리하려고 했습니다. 그런데 구조를 손본 직후, 화면이 하얘지고 콘솔에는 courses.ts 404가 터졌습니다.
“파일 하나만 만들면 되겠지” 하고 courses.ts를 추가했더니, 이번엔 TypeScript가 “내보낸 멤버가 없다”고 알려 줍니다. 한 문제를 막았더니 다른 문제가 생기는 상황, 실무에서 꽤 익숙한 패턴이죠.
이번 글에서는 제가 겪었던 실제 케이스를 바탕으로 아래와 같은 내용을 정리해 보겠습니다. 동일한 폴더/파일 구조를 쓰는 분들께 바로 도움될 내용만 담았습니다.
- 왜 Vite가 courses.ts를 찾아갔는지
- 왜 TS가 export를 못 찾았는지
- 구조를 어떻게 바꾸면 안전한지
| 문제 상황
가장 먼저 발견한 에러는 브라우저 콘솔에 아래 에러가 찍혔습니다.
CourseForm.tsx:21 GET http://localhost:3000/src/shared/api/courses.ts?t=1772149769115 net::ERR_ABORTED 404 (Not Found)
해당 import는 다음과 같았습니다.
import { createCourse, updateCourse } from '@/shared/api/courses';
import { getCourses } from '@/shared/api/courses';
그리고 실제 폴더 구조는 이미 이렇게 되어 있었습니다.
src/shared/api/courses/
├─ index.ts
├─ courses.mapper.ts
└─ courses.types.ts
즉, courses 폴더는 있는데, courses.ts 파일은 없던 상황이었습니다.
| 1차 대응: 404를 막기 위한 임시 파일 추가
Vite가 실제로 요청하는 경로가 courses.ts였기 때문에, 파일을 하나 만들었습니다.
// src/shared/api/courses.ts
export * from './courses';
여기까지는 404가 해결됐습니다. 하지만 바로 다음 오류가 터졌습니다.
| 2차 문제: “내보낸 멤버 없음”
TypeScript가 다음 오류를 출력했습니다.
Module '"@/shared/api/courses"' has no exported member 'getCourses'.
Module '"@/shared/api/courses"' has no exported member 'createCourse'.
Module '"@/shared/api/courses"' has no exported member 'updateCourse'.
즉, re-export가 제대로 되지 않았다는 뜻입니다.
| 원인: 폴더와 같은 이름의 파일이 만든 순환 참조
문제의 핵심은 여기였습니다.
// src/shared/api/courses.ts
export * from './courses';
./courses는 원래 폴더를 가리키길 기대했지만, 같은 레벨에 courses.ts가 생기면서 파일 자신을 다시 참조하게 됩니다.
결과적으로:
- courses.ts → ./courses → 다시 courses.ts
- 실제 index.ts로는 도달하지 못함
- export가 비어 있는 것처럼 보임
즉, 폴더와 같은 이름의 파일을 만든 순간, 모듈 해석이 파일을 우선하게 된 것이 원인이었습니다.
| 해결 방법
파일을 명확하게 index.ts로 연결하도록 수정했습니다.
// src/shared/api/courses.ts
export * from './courses/index';
이렇게 하니, 브라우저의 404는 해결되고, TS의 “export 없음” 오류도 사라졌습니다.
| 실무에서 도움 되는 체크리스트
이 글의 케이스는 실제 프로젝트에서 꽤 재현됩니다.
다음 체크리스트로 미리 예방할 수 있습니다.
- 폴더와 동일한 이름의 파일은 피하세요.
: 특히 index.ts 구조를 쓸 때 더 위험합니다. - re-export는 항상 명시적으로 연결하세요.
: export * from './courses/index' 처럼 분명히 적는 편이 안전합니다. - Vite가 실제로 요청하는 경로를 먼저 확인하세요.
: 브라우저 네트워크 패널이 생각보다 정확한 힌트를 줍니다. - TS 에러는 “실제 import 경로가 다르다”는 신호일 수 있습니다.
: export 오류는 단순 실수보다, 경로 해석 문제일 가능성이 큽니다.
| 결론
이번 문제는 단순한 404 에러처럼 보였지만, 실제로는 폴더/파일 이름 충돌과 잘못된 re-export 경로가 만든 구조적 문제였습니다. Vite와 TS가 서로 다르게 동작하는 지점에서 이런 오류가 자주 발생합니다.
특히 alias 경로나 barrel 구조를 쓰는 프로젝트라면, “어떤 경로가 실제로 resolve 되는지”를 먼저 의심하는 습관이 중요하다고 느꼈습니다.
혹시 비슷하게 아래와 같은 상황을 겪으셨다면, 이 케이스가 바로 원인일 가능성이 높습니다
- 404는 막았는데 TS 오류가 생긴다
- export가 없다고 뜨는데 분명 코드가 있다
- 폴더와 파일 이름이 겹쳐 있다
.
'💻 개발 > 🦕 React' 카테고리의 다른 글
| [React] React + Node.js 풀스택 CRUD 만들기 (Prisma + SQLite) (0) | 2026.03.13 |
|---|---|
| [React] React + Node.js 풀스택 모노레포 환경 세팅 (pnpm + Turborepo) (0) | 2026.03.13 |
| [React] React Router 중첩 라우트에서 index 대신 Navigate를 선택한 이유 (0) | 2026.01.19 |
| [React] React + Vite + TS + Firebase Hosting + Github Action 구축하기 (0) | 2026.01.18 |
| [React] TanStack Query(React Query) 활용하기 - useMutation으로 데이터 생성·수정·삭제 마스터하기 (0) | 2026.01.15 |
