Vite import.meta.env가 undefined일 때 VITE_ 접두사와 실행 경로는 어떻게 확인하나요?
Vite 환경변수가 undefined면 VITE_ 접두사·파일 위치·mode·서버 재시작을 확인하세요. 접두사는 브라우저에 전달할 공개 값에만 씁니다. 작은 앱에서 누락·복구를 비교하며 .env의 서버 비밀값을 공개하지 않습니다.
적용 조건·범위: 하단 누적 TaskFlow의 .env.local, .env.example, src/config/env.js와 src/main.js는 apps/web 폴더 기준입니다. 저장소 루트의 pnpm dev는 @taskflow/web의 Vite script를 실행합니다. 아래 독립 실습은 새 Vite 연습 폴더의 package.json 위치가 실행 루트이며 파일 경로도 그 폴더를 기준으로 읽습니다.
본문 근거: vite.dev/guide/env-and-mode · vite.dev/config/shared-options
독립 실습: 공개 값 누락과 복구
<!doctype html>
<html lang="ko">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>TaskFlow 개발환경</title></head>
<body><main id="app"></main><script type="module" src="/src/main.js"></script></body>
</html>
body { margin: 0; color: #202020; background: #f6f6f6; font: 16px/1.6 system-ui, sans-serif; }
main { max-width: 720px; margin: 64px auto; padding: 24px; }
h1 { font-size: 30px; font-weight: 500; }
p { overflow-wrap: anywhere; }
VITE_API_URL=https://api.example.invalid
VITE_APP_NAME=Env Practice
기대 결과: 표시된 로컬 주소에서 Env Practice 제목과 예제 API URL을 확인합니다. API 호출을 하는 예제가 아닙니다. VITE_API_URL을 API_URL로 바꾸고 재시작하면 필수 값 오류가 발생하며 원래 이름으로 복구하면 정상 화면이 나옵니다.
직접 실행: 환경 준비와 전체 파일
별도 빈 Vite 연습 폴더에 아래 전체 파일을 만듭니다. 이름에 TaskFlow가 들어간 출력 문구도 이 예제 안에서 정의되며 이전 앱이 필요하지 않습니다. .env.local에는 공개 연습 값만 넣습니다.
index.html 전체 코드
package.json 전체 코드
{
"name": "@taskflow/web",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "vite build"
},
"devDependencies": {
"vite": "8.3.0"
}
}
src/config/env.js 전체 코드
const apiUrl = import.meta.env.VITE_API_URL;
const appName = import.meta.env.VITE_APP_NAME;
if (!apiUrl) {
throw new Error('VITE_API_URL 환경 변수가 필요합니다.');
}
export const env = {
apiUrl,
appName: appName || 'TaskFlow',
};
src/main.js 전체 코드
import './style.css';
import { env } from './config/env.js';
const app = document.querySelector('#app');
const title = document.createElement('h1');
title.textContent = env.appName;
const status = document.createElement('p');
status.textContent = '개발환경 스타터가 실행 중입니다.';
const api = document.createElement('p');
api.id = 'api-url';
api.textContent = `API URL: ${env.apiUrl}`;
app.append(title, status, api);
src/style.css 전체 코드
.env.local 전체 코드
pnpm install
pnpm dev
pnpm build
변형 과제: 공개 값 VITE_RETRY_COUNT=3을 추가하고 화면에 Number로 변환한 값과 typeof 원문 값을 나란히 표시합니다. 예상은 3과 string입니다.
오류 재현
.env.local에 다음 값을 넣었다고 가정합니다.
API_URL=http://localhost:4000
코드에서 읽습니다.
console.log(import.meta.env.API_URL);
결과는 undefined가 될 수 있습니다.
Vite는 기본적으로 VITE_ 접두사가 붙은 변수를 클라이언트 코드에 노출합니다.
따라서 다음과 같이 바꿉니다.
VITE_API_URL=http://localhost:4000
console.log(import.meta.env.VITE_API_URL);
왜 접두사가 필요한가
브라우저에서 실행되는 JavaScript는 사용자에게 전달됩니다.
.env 파일에 서버 비밀번호가 있다고 해서 자동으로 안전한 것이 아닙니다.
Vite가 모든 환경 변수를 자동으로 클라이언트에 노출하면 실수로 비밀 값이 번들에 포함될 수 있습니다.
그래서 기본 접두사를 사용해 “이 값은 클라이언트에 공개해도 된다”는 경계를 만듭니다.
VITE_API_URL
→ 클라이언트 노출 가능
DB_PASSWORD
→ 기본 설정에서는 import.meta.env로 클라이언트에 노출하지 않음
단, 이름에 VITE_가 붙었다고 값이 안전해지는 것은 아닙니다.
VITE_가 붙은 값은 공개될 수 있다고 가정해야 합니다.
Vite가 기본으로 제공하는 값
Vite는 사용자 정의 변수 외에도 실행 상태를 확인할 수 있는 값을 제공합니다.
console.log(import.meta.env.MODE);
console.log(import.meta.env.DEV);
console.log(import.meta.env.PROD);
console.log(import.meta.env.BASE_URL);
개발과 배포 코드를 구분할 때 사용할 수 있습니다.
if (import.meta.env.DEV) {
console.log('development mode');
}
환경 변수는 문자열이다
VITE_RETRY_COUNT=3
VITE_FEATURE_FLAG=true
const retryCount = Number(import.meta.env.VITE_RETRY_COUNT);
const featureFlag =
import.meta.env.VITE_FEATURE_FLAG === 'true';
타입 변환 없이 숫자처럼 계산하면 예상하지 못한 결과가 나올 수 있습니다.
envPrefix를 바꿀 수 있나요
Vite 설정의 envPrefix로 기본 접두사를 변경할 수 있습니다.
하지만 특별한 이유가 없다면 기본 VITE_를 유지하는 편이 이해하기 쉽습니다.
특히 envPrefix를 빈 문자열로 설정해 모든 환경 변수를 노출하는 방식은 사용하지 않습니다.
오류 해결 순서
import.meta.env가 undefined일 때 다음 순서로 확인합니다.
1. 환경 변수 이름
2. VITE_ 접두사
3. .env 파일 위치
4. 현재 mode
5. 개발 서버 재시작
6. import.meta.env 철자
7. vite.config의 envDir/envPrefix 변경 여부
자주 발생하는 실수
process.env를 사용합니다
Vite 클라이언트 코드에서는 import.meta.env를 기준으로 사용합니다.
import.meta.env.VITE_API_URL
.env.local 수정 후 서버를 재시작하지 않았습니다
Ctrl + C
pnpm dev
비밀키에 VITE_를 붙였습니다
브라우저에서 사용할 값이 아니라면 클라이언트 변수로 만들지 않습니다.
잘못된 예
VITE_DATABASE_PASSWORD=...
올바른 방향
서버 전용 환경에서만 DB_PASSWORD 사용
boolean처럼 보이는 값을 문자열로 비교하지 않았습니다
const enabled =
import.meta.env.VITE_FEATURE_FLAG === 'true';
학습 완료 체크리스트
- import.meta.env의 역할을 설명할 수 있습니다.
- VITE_ 접두사가 필요한 이유를 이해했습니다.
- 클라이언트 환경 변수에 비밀값을 넣지 않습니다.
- mode와 NODE_ENV를 같은 개념으로 단정하지 않습니다.
- 환경 변수 값을 필요한 타입으로 변환할 수 있습니다.
- undefined 오류를 접두사부터 순서대로 확인할 수 있습니다.
선택 실습: 같은 프로젝트로 연습하기
TaskFlow는 작업을 관리하는 예제 앱입니다. 이 선택 실습에서는 앱을 실행·개발하는 환경과 설정을 준비합니다.
이번 글에서는 TaskFlow에서 VITE_API_URL을 실제 화면에 연결하고, VITE_ 접두사가 빠졌을 때 undefined가 되는 이유를 직접 재현합니다.
선택: 연재 프로젝트와 전체 자료
연재 프로젝트를 이어 만드는 분을 위한 자료입니다. 이 글의 개념과 예제만 찾으셨다면 필요한 파일과 실행 방법만 펼쳐보세요.
TaskFlow 프로젝트 적용
시작 체크포인트: 이전 글의 적용·완료 확인과 이전 회차 시작 ZIP을 기준으로 아래 변경 파일을 수정합니다. 메인 앱은 루트에서 실행하고, labs는 해당 폴더의 package.json과 잠금 파일을 기준으로 따로 실행합니다.
이전 완료본과 달라진 파일: apps/web/src/config/env.js, apps/web/src/main.js, docs/development-environment.md, README.md. 전체 코드에서 같은 경로를 찾아 기존 내용을 대조합니다. ZIP의 자동 생성 lockfile도 해당 패키지 폴더와 함께 유지합니다.
이번 글에서는 TaskFlow에서 VITE_API_URL을 실제 화면에 연결하고, VITE_ 접두사가 빠졌을 때 undefined가 되는 이유를 직접 재현합니다.
3회차의 환경 변수 파일 역할을 알고 있어야 합니다.
.env
.env.local
.env.development
.env.example
TaskFlow 환경 설정 모듈 만들기
src/config/env.js를 만듭니다.
const apiUrl = import.meta.env.VITE_API_URL;
const appName = import.meta.env.VITE_APP_NAME;
if (!apiUrl) {
throw new Error('VITE_API_URL 환경 변수가 필요합니다.');
}
export const env = {
apiUrl,
appName: appName || 'TaskFlow',
};
화면이나 API 모듈에서 import.meta.env를 직접 반복하지 않고 하나의 모듈을 통해 읽습니다.
import { env } from './config/env';
console.log(env.apiUrl);
이 구조를 사용하면 필요한 환경 변수와 fallback을 한곳에서 관리할 수 있습니다.
mode별 파일 확인
개발 서버의 기본 mode는 development입니다.
별도 mode를 사용할 수도 있습니다.
pnpm --filter @taskflow/web exec vite --mode staging
이 경우 .env.staging을 사용할 수 있습니다.
파일이 존재하는 것만 보고 값이 읽힐 것이라고 가정하지 말고 실제 실행 mode를 확인합니다.
이 글을 읽고 바로 확인할 것
독자 질문: Vite의 import.meta.env 값이 undefined일 때 어떤 순서로 확인하나요?
얻을 결과: 파일 위치·변수 접두사·서버 재시작을 구분해 잘못된 읽기를 수정할 수 있습니다.
직접 따라 할 순서
- apps/web의 .env 파일 위치와 변수 이름에 VITE_ 접두사가 있는지 확인합니다.
- 코드에서 import.meta.env.VITE_API_URL을 읽고, 변경 후 개발 서버를 다시 시작합니다.
- pnpm dev에서 표시값을 보고 pnpm build 결과를 확인합니다.
확인 결과 기준: 예제 공개 값이 예상대로 표시되고 빌드가 성공하면 통과입니다. 비밀키에 VITE_를 붙여 클라이언트로 보내지 않습니다.
편집 검증 범위: 이번 보강에서 위 명령과 화면은 다시 실행하지 않았습니다(미확인). 본문 아래의 기존 실행 검증표는 이전 작업에서 기록한 결과이며, 독자는 자신의 환경에서 위 기준으로 확인해 주세요.
이전 체크포인트
dev-env-step-03-env-contract
변경 파일
.env.example
src/config/env.js
src/main.js 또는 앱 진입 파일
.env.example:
VITE_API_URL=https://api.example.invalid
VITE_APP_NAME=TaskFlow
src/config/env.js:
const apiUrl = import.meta.env.VITE_API_URL;
const appName = import.meta.env.VITE_APP_NAME;
if (!apiUrl) {
throw new Error('VITE_API_URL 환경 변수가 필요합니다.');
}
export const env = {
apiUrl,
appName: appName || 'TaskFlow',
};
Before
.env 파일은 있지만 코드에서 직접 검증하지 않음
변수 이름이 틀려도 화면 실행 후에야 문제 발견
After
VITE_ 접두사 기준 확정
환경 변수 모듈 분리
필수 값 누락 시 즉시 오류
브라우저 공개 값과 서버 비밀 값 경계 명확화
실행 확인
pnpm dev
브라우저에서 TaskFlow가 정상 실행되고 env.apiUrl이 예상한 값을 가리키는지 확인합니다.
완료 체크포인트
dev-env-step-04-vite-env
전체 파일·코드 보기
실습 프로젝트 전체 코드
아래는 이번 회차 완료 시점의 전체 소스입니다. 파일 경로는 압축을 푼 체크포인트 폴더를 기준으로 합니다. 자동 생성되는 lockfile은 ZIP에 포함하며, node_modules·빌드 결과·실제 비밀값은 포함하지 않습니다. 메인 앱과 각 lab은 별도의 설치 범위를 유지합니다.
.gitignore
node_modules/
dist/
storybook-static/
.env.local
.env.*.local
apps/web/.env.example
VITE_API_URL=https://api.example.invalid
VITE_APP_NAME=TaskFlow
apps/web/index.html
<!doctype html>
<html lang="ko">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>TaskFlow 개발환경</title></head>
<body><main id="app"></main><script type="module" src="/src/main.js"></script></body>
</html>
apps/web/package.json
{
"name": "@taskflow/web",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1",
"build": "vite build"
},
"devDependencies": {
"vite": "8.3.0"
}
}
apps/web/src/config/env.js
const apiUrl = import.meta.env.VITE_API_URL;
const appName = import.meta.env.VITE_APP_NAME;
if (!apiUrl) {
throw new Error('VITE_API_URL 환경 변수가 필요합니다.');
}
export const env = {
apiUrl,
appName: appName || 'TaskFlow',
};
apps/web/src/main.js
import './style.css';
import { env } from './config/env.js';
const app = document.querySelector('#app');
const title = document.createElement('h1');
title.textContent = env.appName;
const status = document.createElement('p');
status.textContent = '개발환경 스타터가 실행 중입니다.';
const api = document.createElement('p');
api.id = 'api-url';
api.textContent = `API URL: ${env.apiUrl}`;
app.append(title, status, api);
apps/web/src/style.css
body { margin: 0; color: #202020; background: #f6f6f6; font: 16px/1.6 system-ui, sans-serif; }
main { max-width: 720px; margin: 64px auto; padding: 24px; }
h1 { font-size: 30px; font-weight: 500; }
p { overflow-wrap: anywhere; }
docs/development-environment.md
# TaskFlow 개발환경
이 스타터는 개발환경 진단을 위한 최소 Vite/vanilla JavaScript 앱입니다. 할 일 관리 기능이나 API 서버는 포함하지 않습니다.
Node.js 24.19.0, pnpm 11.19.0으로 검증했습니다. 루트는 pnpm만 사용하며 apps/*만 workspace입니다. labs의 npm/Yarn/pnpm 실습은 독립 프로젝트입니다.
## 2회차
메인 앱은 pnpm을 유지합니다. labs/yarn-modern은 독립 Yarn 실습입니다.
## 3회차
apps/web/.env.example을 apps/web/.env.local로 복사합니다. .invalid 주소는 표시만 하며 실제 API 호출은 하지 않습니다. 공개 샘플값만 제공하고 로컬 파일은 ZIP에서 제외합니다.
## 4회차
Vite 루트는 apps/web입니다. env.js가 필수 VITE_API_URL을 검사하고 화면에 공개 URL을 표시합니다.
package.json
{
"name": "taskflow",
"private": true,
"packageManager": "pnpm@11.19.0",
"engines": {
"node": ">=24"
},
"scripts": {
"dev": "pnpm --filter @taskflow/web dev",
"build": "pnpm --filter @taskflow/web build"
}
}
pnpm-workspace.yaml
packages:
- "apps/*"
README.md
# TaskFlow 개발환경 04 완료 체크포인트
Node.js 24.19.0 / pnpm 11.19.0 검증 기준입니다. pnpm을 별도로 설치한 환경에서 실행하세요. ZIP에는 node_modules, dist, 로컬 환경 변수, 실제 비밀값이 없습니다.
1. 이 폴더에서 `pnpm install --frozen-lockfile`
2. PowerShell: `Copy-Item apps/web/.env.example apps/web/.env.local` 또는 macOS/Linux: `cp apps/web/.env.example apps/web/.env.local`
3. `pnpm dev` 후 http://127.0.0.1:5173 확인
4. `pnpm build`
ESLint는 5회차부터 추가되므로 이 체크포인트에는 lint 명령이 없습니다.
apps/web가 Vite 루트입니다. 환경 변수도 이 폴더에 둡니다. 표시용 API 주소에 실제 요청을 보내지 않습니다. labs는 메인 workspace에서 제외되어 각 폴더에서 별도 설치합니다.
원문 단계별 전체 파일과 실행 검증 범위는 docs/development-environment.md를 확인하세요.
labs/yarn-modern/.yarnrc.yml
nodeLinker: node-modules
labs/yarn-modern/package.json
{
"name": "taskflow-yarn-lab",
"private": true,
"type": "module",
"packageManager": "yarn@4.18.0",
"scripts": {
"dev": "node src/main.js",
"lint": "node --check src/main.js"
}
}
labs/yarn-modern/src/main.js
console.log('TaskFlow Yarn 독립 실습');
실행 검증 결과
검증 환경: Windows · Node.js 24.19.0 · pnpm 11.19.0 · Vite 8.3.0.
| 항목 | 확인 결과 |
|---|---|
pnpm install 및 --frozen-lockfile 재설치 |
성공 |
| pnpm build | 성공 |
| pnpm dev | 개발 서버 응답 HTTP 200 확인 |
| Vite 환경 변수 로드 | 변환된 모듈의 VITE_API_URL 값 확인 |
| pnpm lint | 5회차에서 추가 예정 · 이 체크포인트에는 없음 |
환경 변수 모듈의 HTTP 응답과 변환값을 확인했습니다. 모든 브라우저 조합과 운영 배포는 검증하지 않았습니다.
다음 학습
이제 설치와 환경 변수는 재현할 수 있지만 코드 품질 검사를 자동으로 확인하는 기준이 없습니다.
다음 글에서는 eslint.config.js flat config를 연결하고 “VS Code에서는 경고가 없는데 CLI에서는 실패하는 문제”까지 구분합니다.
다음 글: ESLint flat config 규칙 적용 안됨 해결: eslint.config.js 기준
공식 문서
- Vite Env Variables and Modes: https://vite.dev/guide/env-and-mode
- Vite envPrefix: https://vite.dev/config/shared-options
체크포인트 다운로드
글 소개·학습 준비·전체 목차

개발환경 · 4 / 11 · 필수 학습 · Vite 환경 변수 오류 해결
.env.local에 값을 넣었는데 import.meta.env에서 undefined가 나온다면 가장 먼저 변수 이름의 접두사와 현재 실행 mode를 확인해야 합니다.
하단 누적 TaskFlow의 .env.local, .env.example, src/config/env.js와 src/main.js는 apps/web 폴더 기준입니다. 저장소 루트의 pnpm dev는 @taskflow/web의 Vite script를 실행합니다. 아래 독립 실습은 새 Vite 연습 폴더의 package.json 위치가 실행 루트이며 파일 경로도 그 폴더를 기준으로 읽습니다.
이번 글에서 배우는 내용
- Vite의 import.meta.env
- VITE_ 접두사가 필요한 이유
- 브라우저에 노출되는 환경 변수의 보안 경계
- envPrefix의 역할
- 개발 서버 재시작이 필요한 이유
- mode별 env 파일 확인
- undefined 오류 점검 순서
- TaskFlow 환경 설정 모듈 만들기
선수 지식
JavaScript import와 개발 서버 실행을 알고 Vite 프로젝트를 준비합니다. 공통 준비는 pnpm 첫 글을 참고하며 아래 전체 파일로 독립 실행할 수 있습니다.
Node.js·pnpm 설치와 공통 실행 기준은 pnpm 환경 준비에서 확인합니다.
목차
이 글이 도움이 되었나요?
읽음 기록
읽음 — / 전체 11개 · 필수 6개
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.