Next.js는 환경 변수를 다루기 위한 기능을 기본적으로 내장하고 있어요. 이를 통해 다음과 같은 일들을 할 수 있답니다.
경고 (Warning): Next.js의 기본
create-next-app템플릿을 사용하면 모든.env파일이.gitignore에 자동으로 추가됩니다. 이 파일들은 절대로 Git 저장소(Repository)에 커밋(commit)하시면 안 됩니다. 환경 변수에는 비밀번호나 API 키 같은 민감한 정보가 들어있기 때문이죠.
Next.js는 .env* 파일들에 적힌 환경 변수들을 process.env로 불러오는 기능을 기본적으로 지원해요.
//filename=".env"
DB_HOST=localhost
DB_USER=myuser
DB_PASS=mypassword
참고 (Note): Next.js는
.env*파일 안에서 여러 줄(multiline)로 된 변수도 지원한답니다.# .env # 이렇게 줄바꿈을 해서 작성할 수도 있고 PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- ... Kh9NV... ... -----END DSA PRIVATE KEY-----" # 큰따옴표 안에서 `\n`을 사용해서 한 줄로 작성할 수도 있어요 PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nKh9NV...\n-----END DSA PRIVATE KEY-----\n"
참고 (Note): 만약
/src폴더 구조를 사용하고 계신다면 주의해 주세요! Next.js는.env파일들을/src폴더 안이 아니라, 반드시 프로젝트의 최상위(부모) 폴더에서만 불러옵니다.
이렇게 .env 파일에 작성해 두면, Node.js 환경에 process.env.DB_HOST, process.env.DB_USER, process.env.DB_PASS가 자동으로 로드되어서 Route Handlers(라우트 핸들러) 등에서 바로 사용할 수 있게 됩니다.
예를 들어볼까요?
//filename="app/api/route.js"
export async function GET() {
const db = await myDB.connect({
host: process.env.DB_HOST,
username: process.env.DB_USER,
password: process.env.DB_PASS,
})
// ...
}
@next/env를 사용해서 환경 변수 불러오기만약 ORM(예: Prisma 등)의 최상위 설정 파일이나 테스트 러너(Test runner)처럼 Next.js 런타임(실행 환경) 외부에서 환경 변수를 불러와야 할 때가 생길 수 있어요. 그럴 때는 @next/env 패키지를 사용하시면 됩니다.
사실 이 패키지는 Next.js 내부에서도 .env* 파일들을 불러오기 위해 사용하고 있는 도구예요.
사용하려면 먼저 패키지를 설치하고, loadEnvConfig 함수를 사용해 환경 변수를 불러오면 됩니다.
pnpm add @next/env
npm install @next/env
yarn add @next/env
bun add @next/env
설치 후 설정 파일을 작성해 볼까요?
//filename="envConfig.ts" switcher
import { loadEnvConfig } from '@next/env'
const projectDir = process.cwd()
loadEnvConfig(projectDir)
//filename="envConfig.js" switcher
import { loadEnvConfig } from '@next/env'
const projectDir = process.cwd()
loadEnvConfig(projectDir)
이렇게 세팅해 둔 다음, 필요한 곳에서 이 설정을 불러와(import) 사용하시면 됩니다. 예를 들어 ORM 설정 파일에서는 아래처럼 쓸 수 있죠.
//filename="orm.config.ts" switcher
import './envConfig.ts'
export default defineConfig({
dbCredentials: {
connectionString: process.env.DATABASE_URL!,
},
})
//filename="orm.config.js" switcher
import './envConfig.js'
export default defineConfig({
dbCredentials: {
connectionString: process.env.DATABASE_URL,
},
})
Next.js는 .env* 파일 안에서 $ 기호를 사용해 다른 변수를 참조하는 것(예: $VARIABLE)을 자동으로 확장(expand)해 줍니다. 이 기능을 활용하면 다른 시크릿 값들을 쉽게 조합할 수 있어요.
예를 들어:
//filename=".env"
TWITTER_USER=nextjs
TWITTER_URL=https://x.com/$TWITTER_USER
위의 예시에서 process.env.TWITTER_URL을 출력해보면 https://x.com/nextjs로 세팅되어 있는 것을 확인할 수 있습니다.
알아두면 좋은 점 (Good to know): 만약 환경 변수의 '실제 값' 자체에
$문자를 사용해야 한다면, 이스케이프(escape) 처리를 해주셔야 해요. (예:\$)
NEXT_PUBLIC_ 이라는 접두사가 붙지 않은 환경 변수들은 오직 Node.js 환경(서버 측)에서만 사용할 수 있습니다. 즉, 브라우저에서는 접근할 수 없다는 뜻이죠. (클라이언트인 브라우저는 서버와 완전히 다른 '환경'에서 실행되기 때문입니다.)
환경 변수의 값을 브라우저에서도 접근할 수 있게 만들려면, Next.js가 빌드 타임(build time)에 클라이언트로 전달되는 자바스크립트 번들 안에 그 값을 "인라인(inline, 직접 삽입)" 하도록 지시해야 합니다. 그러면 코드 상의 process.env.[variable] 참조가 모두 하드 코딩된 실제 값으로 교체됩니다.
이렇게 지시하는 방법은 아주 간단해요. 변수 이름 앞에 NEXT_PUBLIC_을 붙여주기만 하면 됩니다.
//filename=".env"
NEXT_PUBLIC_ANALYTICS_ID=abcdefghijk
이렇게 하면 Next.js는 next build 명령어를 실행할 때, Node.js 환경에 있는 process.env.NEXT_PUBLIC_ANALYTICS_ID에 대한 모든 참조를 실제 환경 변수 값으로 바꿔치기합니다. 덕분에 클라이언트 측 코드 어디에서든 이 값을 사용할 수 있게 되고, 브라우저로 전송되는 자바스크립트 파일 안에 이 값이 직접 삽입됩니다.
참고 (Note): 앱이 한 번 '빌드'되고 나면, 더 이상 이 환경 변수들의 변경사항에 반응하지 않습니다.
예를 들어, 하나의 환경에서 빌드된 결과물을 다른 환경으로 넘기는 Heroku 파이프라인을 사용하거나, 단일 Docker 이미지를 빌드해서 여러 환경에 배포하는 경우를 생각해 보세요. 모든NEXT_PUBLIC_변수들은 빌드될 시점의 값으로 꽁꽁 얼어붙게(frozen) 됩니다. 따라서 프로젝트를 빌드할 때 상황에 맞는 올바른 값이 세팅되어 있어야 합니다. 만약 실행 중(runtime)에 환경 변수 값이 동적으로 바뀌어야 하고 클라이언트가 그 값을 알아야 한다면, 자체적인 API를 만들어서 클라이언트에게 제공해주어야 합니다 (필요할 때마다 요청하거나, 초기화 과정에서 받아오도록 말이죠).
//filename="pages/index.js"
import setupAnalyticsService from '../lib/my-analytics-service'
// 'NEXT_PUBLIC_ANALYTICS_ID'는 'NEXT_PUBLIC_' 접두사가 있으므로 여기서 사용할 수 있어요.
// 이 코드는 빌드 시점에 `setupAnalyticsService('abcdefghijk')` 처럼 변환됩니다.
setupAnalyticsService(process.env.NEXT_PUBLIC_ANALYTICS_ID)
function HomePage() {
return <h1>Hello World</h1>
}
export default HomePage
주의할 점: 아래처럼 변수를 통해 동적으로 조회(dynamic lookups)하는 경우에는 값이 인라인되지 않습니다!
// 변수를 통해 접근하기 때문에 인라인되지 않습니다! (에러 유발 가능)
const varName = 'NEXT_PUBLIC_ANALYTICS_ID'
setupAnalyticsService(process.env[varName])
// 이 경우도 환경 변수 객체를 변수에 담아 접근하므로 인라인되지 않습니다!
const env = process.env
setupAnalyticsService(env.NEXT_PUBLIC_ANALYTICS_ID)
💡 강사의 팁:
여기서 정말 많은 주니어 개발자분들이 실수를 합니다. 환경 변수를 구조 분해 할당(const { NEXT_PUBLIC_API_KEY } = process.env;)하려고 하거나 변수명으로 동적 접근을 하면 빌드 후에 브라우저에서 undefined가 뜨게 됩니다. 반드시 process.env.NEXT_PUBLIC_변수명 형태로 전체 경로를 하드코딩하듯 명시해 주셔야 번들러가 정확히 인식하고 값을 치환해 줍니다!
Next.js는 빌드 타임 환경 변수와 런타임 환경 변수를 모두 지원합니다.
기본적으로 환경 변수는 서버에서만 사용할 수 있습니다. 브라우저에 환경 변수를 노출하려면 반드시 NEXT_PUBLIC_을 접두사로 붙여야 하죠. 하지만 이렇게 노출된 public 환경 변수들은 next build 과정 중에 자바스크립트 번들에 하드코딩(인라인) 되어버립니다.
만약 동적 렌더링(dynamic rendering) 중이라면 서버 측에서 환경 변수를 안전하게 읽어올 수 있습니다.
//ilename="app/page.ts" switcher
import { connection } from 'next/server'
export default async function Component() {
await connection()
// cookies, headers 및 기타 Dynamic API들을 사용하면
// 동적 렌더링(dynamic rendering)으로 전환되며,
// 이는 이 환경 변수가 런타임(실행 시점)에 평가된다는 것을 의미합니다.
const value = process.env.MY_VALUE
// ...
}
//filename="app/page.js" switcher
import { connection } from 'next/server'
export default async function Component() {
await connection()
// cookies, headers 및 기타 Dynamic API들을 사용하면
// 동적 렌더링(dynamic rendering)으로 전환되며,
// 이는 이 환경 변수가 런타임(실행 시점)에 평가된다는 것을 의미합니다.
const value = process.env.MY_VALUE
// ...
}
이 방식을 사용하면, 하나의 Docker 이미지를 가지고 서로 다른 환경 변수 값을 주입해가며 여러 환경(개발, 스테이징, 운영 등)에 배포하는 것이 가능해집니다.
알아두면 좋은 점:
register 함수(https://nextjs.org/docs/app/guides/instrumentation)를 사용하면 서버가 시작될 때 특정 코드를 실행시킬 수 있습니다.development(개발)와 production(운영) 환경 외에도, test(테스트)라는 세 번째 옵션이 있습니다. 개발이나 운영 환경에 기본값을 설정했던 것과 같은 방식으로, 테스트 환경을 위한 .env.test 파일을 만들 수 있어요 (물론 앞의 두 개보다는 자주 쓰이진 않지만요). Next.js는 testing 환경일 때는 .env.development나 .env.production 파일의 환경 변수를 불러오지 않습니다.
이 기능은 jest나 cypress 같은 도구로 테스트를 돌릴 때, 오직 테스트 목적으로만 특정 환경 변수를 설정해야 할 경우에 아주 유용합니다. NODE_ENV가 test로 설정되어 있으면 테스트 기본값이 로드되는데, 보통 테스트 도구들이 알아서 이 값을 세팅해주기 때문에 여러분이 수동으로 설정할 일은 거의 없습니다.
test 환경은 development나 production 환경과 한 가지 작은 차이점이 있으니 꼭 기억해 두세요! .env.local 파일은 로드되지 않습니다. 테스트는 누가 언제 실행하든 항상 동일한 결과를 내야 하기 때문이죠. .env.local은 개발자 개인의 로컬 환경에 맞게 기본값을 덮어쓰기 위해 존재하는 파일인데, 테스트 시에 이 파일이 로드되면 사람마다 테스트 환경이 달라질 수 있잖아요? 그래서 이를 무시하고 모든 테스트 실행이 동일한 환경 변수 기본값을 사용하도록 보장하는 것입니다.
알아두면 좋은 점 (Good to know): 일반적인 기본 환경 변수 파일들과 마찬가지로
.env.test파일은 Git 저장소에 포함시켜야 하지만,.env.test.local파일은.gitignore를 통해 무시되도록 처리해서 저장소에 올리면 안 됩니다.
단위 테스트(Unit test)를 실행하는 동안에도 Next.js가 환경 변수를 불러오는 것과 동일한 방식으로 환경 변수를 세팅하고 싶다면, @next/env 패키지의 loadEnvConfig 함수를 활용하시면 됩니다.
// 아래 코드는 Jest의 global setup 파일이나
// 이와 유사한 테스트 환경 설정 파일에서 사용할 수 있습니다.
import { loadEnvConfig } from '@next/env'
export default async () => {
const projectDir = process.cwd()
loadEnvConfig(projectDir)
}
Next.js는 다음 순서대로 환경 변수를 탐색하며, 변수를 찾는 즉시 탐색을 멈춥니다. (이 순서는 면접이나 실무 트러블슈팅에서도 매우 중요해요!)
process.env (이미 운영체제나 실행 환경에 설정되어 있는 변수가 1순위입니다).env.$(NODE_ENV).local (예: .env.development.local).env.local (단, NODE_ENV가 test일 때는 확인하지 않아요.).env.$(NODE_ENV) (예: .env.development).env예를 들어볼게요. NODE_ENV가 development인 상태에서 동일한 이름의 변수를 .env.development.local과 .env 두 곳에 모두 정의해두었다면, 우선순위가 더 높은 .env.development.local에 있는 값이 사용됩니다.
알아두면 좋은 점 (Good to know):
NODE_ENV에 허용되는 값은production,development,test세 가지뿐입니다.
/src 디렉토리(https://nextjs.org/docs/app/api-reference/file-conventions/src-folder)를 사용하고 계시더라도, .env.* 파일들은 반드시 프로젝트의 최상위 루트(root) 폴더에 위치해야 합니다.NODE_ENV가 따로 할당되지 않은 경우, Next.js는 next dev 명령어를 실행할 때 자동으로 development를 할당하고, 그 외의 모든 명령어에서는 production을 할당합니다.| 버전 (Version) | 변경 사항 (Changes) |
|---|---|
v9.4.0 | .env 및 NEXT_PUBLIC_ 지원이 도입되었습니다. |
모든 문서의 의미론적 개요(semantic overview)를 보시려면 https://nextjs.org/docs/sitemap.md를 참고해 주세요.
사용 가능한 모든 문서의 색인(index)을 보시려면 https://nextjs.org/docs/llms.txt를 참고해 주세요.