환경변수 .env 파일 차이: local, example, development 기준

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

.env.local·.env.example·.env.development는 어떤 기준으로 나누나요?

.env 파일은 환경마다 달라지는 값을 코드와 분리하지만 비밀정보를 자동으로 보호하지는 않습니다. 실제 로컬 값과 공유용 .env.example을 나누고 사용하는 도구의 로딩 규칙을 확인하세요.

적용 조건·범위: 프로젝트마다 도구의 로딩 규칙은 다를 수 있지만, Vite를 사용하는 TaskFlow에서는 다음 기준으로 이해합니다.

본문 근거: vite.dev/guide/env-and-mode

독립 실습: 환경 변수 파일 계약

새 연습 폴더에 아래 세 파일을 만듭니다. 실제 비밀 값은 사용하지 않습니다. 이 실습은 파일 역할과 공유 범위 확인이며 브라우저 로드 실습은 다음 Vite 글의 독립 예제에서 진행합니다.

.env.example 전체 코드

VITE_APP_NAME=Example App

.env.local 전체 코드

VITE_APP_NAME=Local Practice

.gitignore 전체 코드

.env.local
.env.*.local

기대 결과: 공유 문서에는 Example App, 개인 설정에는 Local Practice가 있습니다. .env.example은 Vite가 자동 로드하는 실행 설정이 아닙니다. 기존 Git 연습 저장소가 있다면 git check-ignore .env.local로 제외 규칙을 읽기 확인할 수 있지만 이 과제 때문에 Git을 새로 초기화할 필요는 없습니다.

변형 과제: 공개 변수 VITE_APP_NAME을 추가하면 .env.example과 개인 .env.local 중 어떤 파일에 어떤 값을 적을지 정합니다. 서버 비밀번호에는 VITE_를 붙이지 않는 이유도 설명합니다.

환경 변수는 왜 사용하는가

코드 안에 환경별 값을 직접 작성하면 개발, 테스트, 배포 환경을 바꿀 때 소스 코드를 수정해야 합니다.

const apiUrl = 'http://localhost:3000';

환경 변수로 분리하면 실행 환경에 따라 값을 바꿀 수 있습니다.

const apiUrl = import.meta.env.VITE_API_URL;

하지만 환경 변수 파일은 비밀 저장소가 아닙니다. 특히 브라우저 번들에 포함되는 변수는 사용자가 확인할 수 있습니다.

파일 역할 먼저 구분하기

프로젝트마다 도구의 로딩 규칙은 다를 수 있지만, Vite를 사용하는 TaskFlow에서는 다음 기준으로 이해합니다.

.env
→ 모든 mode에서 사용할 공통 기본값

.env.local
→ 현재 PC에서만 사용할 로컬 덮어쓰기 값

.env.development
→ development mode 전용 값

.env.production
→ production mode 전용 값

.env.example
→ 저장소에 공유할 변수 이름과 예시 형식

.local 계열 파일은 개인 환경 값을 담는 용도로 사용하고 일반적으로 Git에서 제외합니다.

.env.example은 왜 필요한가

.env 파일을 모두 .gitignore에 넣기만 하면 새 개발자는 어떤 변수가 필요한지 알 수 없습니다.

그래서 실제 값이 없는 .env.example을 함께 관리합니다.

VITE_API_URL=https://example.invalid
VITE_APP_NAME=TaskFlow

새 환경에서는 이 파일을 보고 필요한 변수를 준비합니다.

.env.example
→ 어떤 변수 이름이 필요한지 공개

.env.local
→ 내 PC의 실제 값

실제 비밀 값은 넣지 않기

다음 값은 예시 문서나 공개 저장소에 넣지 않습니다.

비밀번호
개인 액세스 토큰
서버 전용 secret
service account private key
실제 API secret

또한 브라우저에서 읽는 환경 변수는 변수 이름에 secret이 들어간다고 해서 비밀이 되지 않습니다.

클라이언트 번들에 들어가는 값은 사용자가 볼 수 있다고 가정해야 합니다.

값은 문자열로 읽힌다

환경 변수에 숫자나 boolean처럼 보이는 값을 넣어도 클라이언트에서 문자열로 다뤄질 수 있습니다.

VITE_PAGE_SIZE=20
VITE_FEATURE_ENABLED=true

코드에서 필요한 타입으로 변환합니다.

const pageSize = Number(import.meta.env.VITE_PAGE_SIZE);
const featureEnabled =
  import.meta.env.VITE_FEATURE_ENABLED === 'true';

변환에 실패했을 때의 처리도 필요합니다.

const rawPageSize = Number(import.meta.env.VITE_PAGE_SIZE);
const pageSize = Number.isFinite(rawPageSize)
  ? rawPageSize
  : 20;

환경 변수가 안 읽힐 때 확인 순서

1. 파일 이름 확인
2. 프로젝트 루트 위치 확인
3. 현재 mode 확인
4. 변수 prefix 확인
5. 개발 서버 재시작
6. 실제 코드 참조 위치 확인

무조건 .env 파일을 여러 개 복사하거나 변수 이름을 바꾸는 방식으로 해결하지 않습니다.

.env 파일을 수정했는데 화면이 안 바뀝니다

환경 변수는 개발 서버가 시작될 때 읽히는 경우가 많습니다.

서버를 재시작해 확인합니다.

Ctrl + C
pnpm dev

process.env를 쓰면 되나요

Vite 클라이언트 코드에서는 기본적으로 import.meta.env를 기준으로 사용합니다.

import.meta.env.VITE_API_URL

Node.js 서버 코드나 다른 프레임워크는 별도 규칙을 사용할 수 있습니다. 환경 변수 접근 문법을 프로젝트 도구와 섞지 않는 것이 중요합니다.

학습 완료 체크리스트

  • .env와 .env.local의 역할을 구분할 수 있습니다.
  • .env.development 같은 mode별 파일을 이해했습니다.
  • .env.example에 실제 비밀 값을 넣지 않습니다.
  • 브라우저 환경 변수는 비밀 저장소가 아니라는 점을 이해했습니다.
  • 환경 변수 값이 문자열로 읽힐 수 있음을 알고 있습니다.
  • 환경 변수 로딩 문제를 파일 이름부터 순서대로 확인할 수 있습니다.

선택 실습: 같은 프로젝트로 연습하기

TaskFlow는 작업을 관리하는 예제 앱입니다. 이 선택 실습에서는 앱을 실행·개발하는 환경과 설정을 준비합니다.

이번 글에서는 TaskFlow의 환경 변수 계약을 만들고 .env, .env.local, .env.development, .env.example의 역할을 구분합니다.

선택: 연재 프로젝트와 전체 자료

연재 프로젝트를 이어 만드는 분을 위한 자료입니다. 이 글의 개념과 예제만 찾으셨다면 필요한 파일과 실행 방법만 펼쳐보세요.

TaskFlow 프로젝트 적용

시작 체크포인트: 이전 글의 적용·완료 확인과 이전 회차 시작 ZIP을 기준으로 아래 변경 파일을 수정합니다. 메인 앱은 루트에서 실행하고, labs는 해당 폴더의 package.json과 잠금 파일을 기준으로 따로 실행합니다.

이전 완료본과 달라진 파일: apps/web/.env.example, docs/development-environment.md, README.md. 전체 코드에서 같은 경로를 찾아 기존 내용을 대조합니다. ZIP의 자동 생성 lockfile도 해당 패키지 폴더와 함께 유지합니다.

이 글의 TaskFlow 예제에서 .env.example과 .env.local의 위치는 apps/web입니다. 루트에서 pnpm dev를 실행하면 @taskflow/web 앱 폴더를 기준으로 Vite가 실행되므로, 환경 변수 파일도 해당 앱 폴더에서 관리합니다.

이번 글에서는 TaskFlow의 환경 변수 계약을 만들고 .env, .env.local, .env.development, .env.example의 역할을 구분합니다.

package.json과 개발 서버 실행 방법을 알고 있으면 됩니다.

pnpm dev

TaskFlow 예제

.env.example:

VITE_API_URL=https://api.example.invalid
VITE_APP_NAME=TaskFlow

.env.local:

VITE_API_URL=http://localhost:4000
VITE_APP_NAME=TaskFlow Local

실제 프로젝트에서는 .env.local을 Git에 올리지 않습니다.

.gitignore 예시:

.env.local
.env.*.local

필요에 따라 팀의 실제 정책과 사용하는 도구 기준으로 조정합니다.

mode는 무엇인가

Vite는 기본적으로 개발 서버와 빌드에서 서로 다른 mode를 사용할 수 있습니다.

pnpm dev
pnpm build

별도 mode를 지정할 수도 있습니다.

pnpm --filter @taskflow/web exec vite --mode staging

그러면 .env.staging 같은 파일을 사용할 수 있습니다.

중요한 점은 NODE_ENV와 Vite의 mode를 같은 개념으로 외우지 않는 것입니다. Vite 공식 문서에서도 두 개념을 구분합니다.

이 글을 읽고 바로 확인할 것

독자 질문: .env, .env.local, .env.development, .env.example을 어디에 두고 무엇을 공유하나요?

얻을 결과: 파일 이름만으로 자동 로드를 가정하지 않고, 예제 파일과 로컬 비밀값을 분리할 수 있습니다.

직접 따라 할 순서

  1. apps/web/.env.example에서 예제용 변수 이름과 공개해도 되는 값을 확인합니다.
  2. 로컬에서 필요한 값만 .env.local로 옮기고 .gitignore를 확인합니다.
  3. Vite 앱에서 실제 노출을 테스트하기 전에는 이름·값의 공유 범위를 점검합니다.

확인 결과 기준: 저장소에는 실제 비밀값이 없고 예제 파일만 공유되면 이 단계가 끝납니다. 브라우저 노출 확인은 4회차의 실습으로 이어집니다.

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

다음 학습: Vite import.meta.env undefined 해결: VITE_ prefix 기준

이전 체크포인트

dev-env-step-02-package-manager-choice

현재 상태

TaskFlow의 패키지 매니저는 pnpm으로 고정됐지만 실행 환경별 설정값 계약은 아직 없습니다.

이번 변경

.env.example 추가
.env.local Git 제외 기준 정리
VITE_API_URL 정의
VITE_APP_NAME 정의
환경 변수 문서화

변경 파일

.env.example
.gitignore
docs/development-environment.md

.env.example:

VITE_API_URL=https://api.example.invalid
VITE_APP_NAME=TaskFlow

검증

실제 .env.local을 준비하고 개발 서버를 재시작합니다.

pnpm dev

이번 회차에서는 파일 계약까지만 확정합니다. 실제로 import.meta.env에서 값이 노출되는지와 VITE_ prefix 문제는 다음 회차에서 확인합니다.

완료 체크포인트

dev-env-step-03-env-contract
전체 파일·코드 보기

실습 프로젝트 전체 코드

아래는 이번 회차 완료 시점의 전체 소스입니다. 파일 경로는 압축을 푼 체크포인트 폴더를 기준으로 합니다. 자동 생성되는 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/main.js

import './style.css';

const app = document.querySelector('#app');
const title = document.createElement('h1');
title.textContent = 'TaskFlow';
const status = document.createElement('p');
status.textContent = '개발환경 스타터가 실행 중입니다.';
app.append(title, status);

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에서 제외합니다.

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 개발환경 03 완료 체크포인트

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 lint 5회차에서 추가 예정 · 이 체크포인트에는 없음
.env.example 공개 예제값과 앱 폴더 위치 확인
브라우저의 환경 변수 사용 4회차에서 연결·검증

메인 앱의 개발 서버 HTTP 응답은 같은 Vite 구성을 사용하는 1회차에서 확인했습니다. 이 회차에서는 위에 표시한 명령과 설정 범위만 별도로 확인했습니다.

다음 학습

다음 글에서는 환경 변수 파일은 정상인데 import.meta.env 값이 undefined로 나오는 문제를 재현합니다.

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

공식 문서

체크포인트 다운로드

dev-env-step-03-env-contract 완료 ZIP 다운로드

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

글 소개·학습 준비·전체 목차
환경변수 파일 차이를 표현한 폴더와 곰 표지

개발환경 · 3 / 11 · 필수 학습 · 환경 변수 파일 구분

.env 파일을 처음 사용할 때 가장 많이 생기는 문제는 “어떤 파일에 무엇을 넣어야 하는지”보다 “이 값이 브라우저에 노출되는지, 저장소에 올려도 되는지”를 구분하지 못하는 것입니다.

이번 글에서 배우는 내용

  • 환경 변수가 필요한 이유
  • .env와 .env.local 차이
  • mode별 .env 파일
  • .env.example의 역할
  • Git에 포함할 파일과 제외할 파일
  • 공개 값과 비밀 값 구분
  • 문자열로 읽히는 환경 변수 처리
  • TaskFlow 환경 변수 계약 만들기

선수 지식

파일 생성과 .gitignore의 역할을 알고 시작합니다. 파일 계약 실습에는 서버 실행이나 이전 TaskFlow 파일이 필요하지 않습니다.

목차

  1. 환경 변수는 왜 사용하는가
  2. 파일 역할 먼저 구분하기
  3. .env.example은 왜 필요한가
  4. 실제 비밀 값은 넣지 않기
  5. 값은 문자열로 읽힌다
  6. 환경 변수가 안 읽힐 때 확인 순서
  7. .env 파일을 수정했는데 화면이 안 바뀝니다
  8. process.env를 쓰면 되나요
  9. 학습 완료 체크리스트
  10. 독립 실습: 환경 변수 파일 계약
  11. TaskFlow 프로젝트 적용
  12. TaskFlow 예제
  13. mode는 무엇인가
  14. 이 글을 읽고 바로 확인할 것
  15. 실습 프로젝트 전체 코드
  16. 실행 검증 결과
  17. 다음 학습
  18. 공식 문서

이 글이 도움이 되었나요?

조회 중

읽음 기록

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

이 글을 쓴 해비

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

새 글 받아보기

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

RSS 피드 구독하기

댓글 남기기