똑같은 삽질은 2번 하지 말자

프론트엔드에서 환경변수에 대한 고찰 본문

카테고리 없음

프론트엔드에서 환경변수에 대한 고찰

곽빵 2025. 2. 26. 14:04

환경변수와 빌드/런타임 개념의 이해

환경변수(Environment Variables)란?

어플리케이션 내부에서 직접 소스코드로 하드코딩하지 않고, 필요에 따라 유연하게 값을 바꿔 쓸 수 있도록 설정해두는 변수이다. 일반적으로 API 엔드포인트, 비밀 키, 접근 토큰 등의 민감 정보나 배포 환경마다 다른값을 설정한다.

빌드 타임(Build Time)과 런타임(Run Time)

  • 빌드 타임: 애플리케이션이 최종 형태로 패키징(빌드)되는 시점. Next/Nuxt에서 SSG(Static Site Generation) 페이지를 생성할 때도 이 시점에 포함된다.
  • 런타임: 빌드된 애플리케이션을 실제 서버나 브라우저에서 구동하는 시점이다. SSR(Server-Side Rendering)이나 CSR(Client-Side Rendering) 중 실행되어야 할 코드를 처리하는 시간대가 여기에 해당된다

빌드 타임에 필요한 환경변수

정적 페이지(SSG)에서 하드코딩되는 환경변수

Next.js나 Nuxt.js에서 정적 페이지(SSG)를 생성할 때는, 빌드 과정을 거치며 getStaticProps 혹은 nuxt generate 과정에서 관련 데이터가 미리 HTML로 생성되어진다.

  • 이때 사용된 환경변수들은 빌드 시점에 하드코딩(치환)된 형태로 결과물에 포함된다.
  • 따라서 해당 환경변수를 빌드 후에 변경하고 싶어도, 이미 정적으로 생성되어버린 페이지에는 반영하기 어렵다.
pages/index.js
export async function getStaticProps() {
  const apiUrl = process.env.API_URL; // 빌드 타임에 이미 치환
  const res = await fetch(apiUrl);
}

위 apiUrl은 빌드 타임에 고정되므로, 빌드된 후에 이 값을 바꾸고 싶다면 다시 빌드를 해야 한다.

런타임에 필요한 환경변수

SSR과 CSR에서 참조되는 동적인 값

SSR(Server-Side Rendering)이나 CSR(Client-Side Rendering) 중에는 앱이 실제로 동작할 때 사용되는 값이 필요하다.

  • SSR: 서버에서 요청을 받을 때마다 렌더링이 일어나므로, 런타임에 환경변수를 불러와야 동적으로 변경이 가능
  • CSR: 클라이언트 측에서 동작하는 로직일 경우, 빌드 타임에 정적으로 넣어둘 수 없는 정보를 로드해야 할 때가 있다.

Nuxt에서는 Runtime Config 기능(예: publicRuntimeConfig, privateRuntimeConfig)을 제공하여 런타임에 환경변수를 주입할 수 있다.

nuxt.config.js
export default {
  publicRuntimeConfig: {
    baseURL: process.env.BASE_URL || 'https://default.url.com'
  }
}
 

이렇게 설정하면, 배포 후 BASE_URL을 변경해도 서버를 다시 시작할 때마다 해당 값을 읽어와 적용할 수 있다. (빌드 과정에서 하드코딩되지 않으므로 유연성이 생긴다)

왜 빌드 타임과 런타임을 구분해야 할까?

  1. 보안 및 민감 데이터
    • 클라이언트(브라우저)에서 노출되면 안되는 값들.
  2. 환경에 따른 설정 차이
    • 개발 환경, 스테이징, 프로덕션 환경에서 각각 다른 API 엔드포인트나 토큰을 쓰는 경우가 많은데 이것들이 빌드 시점에 확정지어도 되는지(SSG), 혹은 실제 서버가 돌 때마다 달라지는지(SSR/CSR)에 따라 구현이 달라진다.

사용 예시

NextJS의 경우

  • 빌드 시 필요:
    • getStaticProps, getStaticPaths 등에서 사용하는 API URL, 키
  • 런타임 시 필요:
    • 서버에서 요청을 처리할 때(예: getServerSideProps) 환경 변수로부터 특정 값을 읽어야 하는 경우
    • 클라이언트에서 .env 변수를 직접 참조하면 빌드 결과에 그대로 포함되므로, 노출 가능한 변수만 NEXT_PUBLIC_XXX 형태로 사용하는 것을 권장합니다.

NuxtJS의 경우

  • 빌드 시 필요:
    • nuxt generate로 SSG를 생성할 때만 쓰는 통계성 API 키, 빌드 타임 설정값
  • 런타임 시 필요:
    • publicRuntimeConfig, privateRuntimeConfig에 정의해두고 서버 실행 시 주입되는 값
    • API Credential이나 DB 커넥션 정보 등은 노출 범위를 철저히 구분해야 합니다(privateRuntimeConfig 사용).

주의사항

Webpack DefinePlugin과 process.env 동적 참조 이슈

1) Client Side에서의 process.env

  • Server Side(Node.js 환경)에서는 process.env를 자유롭게 읽을 수 있지만, Client Side(브라우저)에서는 직접적으로 접근하기 어렵습니다.
  • Next.js나 Nuxt.js 같은 프레임워크의 빌드 도구(주로 Webpack)는 DefinePlugin을 사용해, 코드 안의 process.env.XXX를 빌드 시점에 문자열로 치환하는 방식을 씁니다.

2) 왜 동적 접근(process.env[envVar])은 불가능할까?

  • DefinePlugin은 정적인 키 이름을 인식해 코드를 치환합니다.
    예를 들어, process.env.SAMPLE_FLAG 또는 process.env["SAMPLE_FLAG"]는 빌드 타임에 SAMPLE_FLAG의 실제 값으로 대체됩니다.
  • 그러나 동적 접근(process.env[envVar])처럼 컴파일 시점에 키 이름을 알 수 없는 경우에는 어떤 값을 치환해야 할지 모르기 때문에, 빌드 결과물에 반영되지 않습니다.
// 다음은 빌드 시점에 DefinePlugin이 인식하여 치환 가능
process.env.SAMPLE_FLAG // OK
process.env["SAMPLE_FLAG"] // OK

// 다음은 동적 참조로 인해 빌드할 때 키 이름이 불명확하므로 치환 불가
const envVar = toUpperCaseFromCamelCase(key);
process.env[envVar] // NG

 

 

3) 실제 발생할 수 있는 문제들

  1. 빌드 후 클라이언트에 반영되지 않음:
    빌드 과정에서 치환되지 않은 변수는 결과적으로 런타임에 undefined가 되어 의도한 기능이 작동하지 않을 수 있습니다.
  2. 민감 정보 노출 위험:
    DefinePlugin으로 치환된 환경변수는 클라이언트 번들 안에 하드코딩되어 들어가므로, 비밀 키나 민감 데이터는 절대 넣으면 안 됩니다.

4) 어떻게 처리할까?

  • 정적 키 사용: client side에서 환경변수가 필요하다면, process.env.MY_KEY처럼 코드를 직접 써서 빌드 시점에 치환되도록 합니다.
  • 런타임 Config 사용: Nuxt.js의 publicRuntimeConfig, Next.js 서버의 API 라우트, 또는 서버에서만 노출 가능한 값들은 SSR에서 처리하고 클라이언트에는 필요한 최소한의 정보만 전달하도록 설계합니다.
  • 서버 프록시 활용: 민감하거나 동적으로 바뀌는 환경변수는 서버 단에서만 관리하고, 클라이언트는 서버 API를 호출하는 방식으로 값을 받도록 만들 수도 있습니다.

마무리

정적 페이지 생성(SSG)과 서버 사이드 렌더링(SSR), 클라이언트 사이드 렌더링(CSR) 각각의 시점에 따라 환경변수를 어떻게 활용해야 할지는 Next/Nuxt 사용에서 중요한 부분이다.

  • 빌드 시점에 값을 고정해야 하는 부분과
  • 런타임 시점에 동적으로 설정해야 하는 부분

위 부분들이 확실히 구분되어야, 배포 후에 예상치 못한 버그나 정보 노출 문제를 예방할 수 있다

이처럼 환경변수의 적용 범위와 시점을 정확히 이해하면, 더 안전하고 유연한 어플리케이션을 만들 수 있다.

Comments