환경변수(env) 타입 검증을 하는법??

eeensu·2025년 6월 8일

Typescript

목록 보기
24/25

환경변수에 안전하게 접근하고 싶다면...

react / next 로 개발을 하다가 보면, 환경변수를 사용하여 개발해야하는 상황이 생기고, 그때의 접근은 process.env.ENVIRONEMNT_VALUE 로 접근한다. 하지만, 이러한 방식의 접근은 다음의 두가지 불편한 점이 있다.

  • 자동완성 (타입) 이 이루어지지 않는다.
  • .env 파일에 해당 값이 없을 경우, 원치 않는 상황에서 에러를 발생시킨다.

이러한 상황에 도움을 줄 라이브러리를 발견했다. ts 환경에서 안전하게 환경변수에 접근할 수 있도록 해주면서, 해당 환경변수 값이 존재하지 않을 시 보다 안정된 에러를 발생시켜주는 유용한 친구인 @t3-oss/env-nextjs 이다.

이 라이브러리는 스키마 정의 라이브러리인 'zod' 와 결합이 되어, 타입 검증과 함께 사용되며, 잘 활용하면 환경 변수의 유형을 정의하고, 유효성을 검사하여 오류나 누락된 값에 대한 방어 코드를 작성할 수 있다.




또한 이외에도 유용한 장점들을 갖고 있다.

  • zod 스키마를 통해 각 환경 변수의 타입을 엄격히 검사한다.

  • 어떤 변수는 클라이언트에서, 어떤 변수는 서버에서만 접근 가능하도록 구분하여 보안을 강화한다.

  • 필수 환경 변수가 없거나 타입이 잘못된 경우, 빌드 타임에 오류를 발생시켜 문제를 조기에 발견할 수 있다.

  • 개발, 스테이징, 프로덕션 환경에 맞춘 설정이 가능하다.



프로젝트에 설정하기

환경 변수를 정의하고 검증할 env.ts 파일을 생성하며 다음과 같이 예시를 들 수 있다.

client와 server 객체를 사용하여 환경 변수를 정의한다. client에 정의된 변수는 클라이언트에서 접근할 수 있으며, server 변수는 서버에서만 접근 가능하다.

import { createEnv } from '@t3-oss/env-nextjs';
import { z } from 'zod';

export const env = createEnv({
  client: {
    NEXT_PUBLIC_API_URL: z.string().url(),
    NEXT_PUBLIC_ANALYTICS_ID: z.string(),
  },
  server: {
    DATABASE_URL: z.string().url(),
    SECRET_KEY: z.string(),
  },
  runtimeEnv: process.env, // 런타임 환경 변수를 process.env에서 가져온다.
});
  • 클라이언트 변수 : client에 정의된 변수는 Next.js 규칙에 따라 NEXTPUBLIC 접두사로 시작해야 한다. 이 접두사를 통해 안전하게 클라이언트에 노출할 수 있다.
  • 서버 변수 : server에 정의된 변수는 서버 전용이므로 클라이언트에서 접근할 수 없도록 하며 보안을 강화시주는 유용한 친구다.

이렇게 정의하고 나면 아래와 같이 안전하게 타입과 같이 환경변수에 접근이 가능하다.

env.NEXT_PUBLIC_ANALYTICS_ID
env.DATABASE_URL
env.SECRET_KEY


client env 와 server env를 분리할 수도 있을까?

위에서 설정한 env세팅은, client와 server env를 하나의 바구니에서 정의하고 관리하였다. 하지만, 더 나아가 client env 세팅 따로, server env 세팅도 따로 아예 독립적으로 분리시킬 수도 있다.

// clientEnv.ts
import { createEnv } from '@t3-oss/env-nextjs';
import { z } from 'zod';

export const clientEnv = createEnv({
  client: {
    NEXT_PUBLIC_API_URL: z.string().url(),
    NEXT_PUBLIC_ANALYTICS_ID: z.string(),
  },
  runtimeEnv: {
    NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
    NEXT_PUBLIC_ANALYTICS_ID: process.env.NEXT_PUBLIC_ANALYTICS_ID,
  },
});
// serverEnv.ts
import { createEnv } from '@t3-oss/env-nextjs';
import { z } from 'zod';

export const serverEnv = createEnv({
  server: {
    DATABASE_URL: z.string().url(),
    SECRET_KEY: z.string(),
  },
  runtimeEnv: {
    DATABASE_URL: process.env.DATABASE_URL,
    SECRET_KEY: process.env.SECRET_KEY,
  },
});

이러한 방식들의 차이점을 정리해보자면,

하나의 env에서 관리를 한다면 단순하고 접근하기 쉽다는 점이 있지만, 관리해야하는 환경 변수가 많아지면 가독성이 떨어지고, 또 클라이언트 측 코드에서 서버 전용 환경변수가 개발자의 실수로 노출될 가능성도 생긴다.

env 세팅을 client 와 server로 분리하면 보안을 한층 더 강화시킬 수 있으며 관리하는 환경변수를 더 직관적으로 구분짓기 쉽다는 장점이 생긴다.

따라서, 독립적으로 분리하여 관리하는 방식이 보안과 유지보수 측면에서 더 유리한 경우가 많다고 생각한다. 다만 요구사항과 규모에 따라 적절한 방법은 다를 수 있는 점을 인지해야한다.

profile
안녕하세요! 프론트엔드 개발자입니다! (2024/03 ~)

0개의 댓글