Vite import.meta.env undefined 해결: VITE_ prefix 기준

··설명 약 10분·작성: 해비·블로그 소개

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일 때 어떤 순서로 확인하나요?

얻을 결과: 파일 위치·변수 접두사·서버 재시작을 구분해 잘못된 읽기를 수정할 수 있습니다.

직접 따라 할 순서

  1. apps/web의 .env 파일 위치와 변수 이름에 VITE_ 접두사가 있는지 확인합니다.
  2. 코드에서 import.meta.env.VITE_API_URL을 읽고, 변경 후 개발 서버를 다시 시작합니다.
  3. pnpm dev에서 표시값을 보고 pnpm build 결과를 확인합니다.

확인 결과 기준: 예제 공개 값이 예상대로 표시되고 빌드가 성공하면 통과입니다. 비밀키에 VITE_를 붙여 클라이언트로 보내지 않습니다.

편집 검증 범위: 이번 보강에서 위 명령과 화면은 다시 실행하지 않았습니다(미확인). 본문 아래의 기존 실행 검증표는 이전 작업에서 기록한 결과이며, 독자는 자신의 환경에서 위 기준으로 확인해 주세요.

다음 학습: ESLint flat config 규칙 적용 안됨 해결: eslint.config.js 기준

이전 체크포인트

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 기준

공식 문서

체크포인트 다운로드

dev-env-step-04-vite-env 완료 ZIP 다운로드

이전 회차 시작 체크포인트 ZIP

글 소개·학습 준비·전체 목차
Vite 환경변수 오류 해결을 표현한 설정 창과 곰 표지

개발환경 · 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 환경 준비에서 확인합니다.

목차

  1. 오류 재현
  2. 왜 접두사가 필요한가
  3. Vite가 기본으로 제공하는 값
  4. 환경 변수는 문자열이다
  5. envPrefix를 바꿀 수 있나요
  6. 오류 해결 순서
  7. 자주 발생하는 실수
  8. 학습 완료 체크리스트
  9. 독립 실습: 공개 값 누락과 복구
  10. TaskFlow 프로젝트 적용
  11. TaskFlow 환경 설정 모듈 만들기
  12. mode별 파일 확인
  13. 이 글을 읽고 바로 확인할 것
  14. 실습 프로젝트 전체 코드
  15. 실행 검증 결과
  16. 다음 학습
  17. 공식 문서

이 글이 도움이 되었나요?

조회 중

읽음 기록

읽음 — / 전체 11개 · 필수 6개

이 글을 쓴 해비

웹사이트를 제작·운영하고, 개인 서비스와 자동화·AI 도구를 만듭니다. 직접 만들며 배운 내용과 문제 해결 과정을 BlogFlow에 기록합니다.

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기