개발자
류준열
next-runtime-env 원리 파헤치기
Next와 React에서는 환경변수가 빌드타임에 주입된다. 그런데 쿠버네티스에서 주입하는 환경변수는 도커 이미지가 빌드 된 후, 런타임에 주입된다. 이렇게 주입된 환경변수는 undefined가 된다. 이걸 해결하려면 빌드전에 환경변수를 주입하면 된다. (react도 마찬가지다.)
하지만 빌드없이 환경변수를 변경하기 위해 런타임 환경변수를 이용하는 상황이라면 어떻게 해야 할까?
그럴땐 next-runtime-env라는 것을 사용하면 된다.
(You saved my life, 내가 적은 답변이다.)
런타임 환경변수 재현해보기
나는 쿠버네티스를 할 줄 몰라서 docker-compose로 대신했다.
Next.js와 dockerfile을 간단히 만들고 docker-compose.yaml에 환경변수를 다음과 같이 넣어주었다.
version: "3" services: app: build: context: . dockerfile: Dockerfile container_name: nextjs-app environment: NEXT_PUBLIC_API_URL: https://koreanjson.com/ ports: - "3000:3000"
이렇게 하고 RCC를 만들었다.
"use client";
import Image from "next/image";
import styles from "./page.module.css";
import { env } from "next-runtime-env";
export default function Home() {
return (
<div className={styles.page}>
<main className={styles.main}>
<div>런타임 환경변수: {env("NEXT_PUBLIC_API_URL")}</div>
<div>빌드타임 환경변수: {process.env.NEXT_PUBLIC_API_URL}</div>
...
</div>
);
}
빌드타임 환경변수는 출력되지 않고 런타임 환경변수만 출력된다.
next-runtime-env 까보기
사용법
next-runtime-env의 사용법을 보면 최상단 layout.tsx
의 head태그에 <PublicEnvScript />
를 삽입하고 런타임 환경변수를 사용하는 곳에서 env
함수를 사용하면 된다.
// app/layout.tsx
import { PublicEnvScript } from 'next-runtime-env';
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<PublicEnvScript />
</head>
<body>
{children}
</body>
</html>
);
}
// app/client-page.tsx
'use client';
import { env } from 'next-runtime-env';
export default function SomePage() {
const NEXT_PUBLIC_FOO = env('NEXT_PUBLIC_FOO');
return <main>NEXT_PUBLIC_FOO: {NEXT_PUBLIC_FOO}</main>;
}
<PublickEnvScript />
와 env
함수를 까보자.
PublicEnvScript
export const PublicEnvScript: FC<PublicEnvScriptProps> = ({ nonce }) => {
noStore(); // Opt into dynamic rendering
// This value will be evaluated at runtime
const publicEnv = getPublicEnv();
return <EnvScript env={publicEnv} nonce={nonce} />;
};
알수가 없으니 getPublicEnv와 EnvScript도 살펴보자.
getPublicEnv
import { ProcessEnv } from '../typings/process-env';
/**
* Gets a list of environment variables that start with `NEXT_PUBLIC_`.
*/
export function getPublicEnv() {
const publicEnv = Object.keys(process.env)
.filter((key) => /^NEXT_PUBLIC_/i.test(key))
.reduce(
(env, key) => ({
...env,
[key]: process.env[key],
}),
{} as ProcessEnv,
);
return publicEnv;
}
process.env에 접근하고 NEXT_PUBLIC_
으로 필터해서 가공 후 리턴한다. (process.env로 런타임 환경변수에 어떻게 접근한거지? 라는 의문이 드는데 일단 넘어간다.)
EnvScript
/**
* Sets the provided environment variables in the browser. If an nonce is
* available, it will be set on the script tag.
*
* Usage:
* ```ts
* <head>
* <EnvScript env={{ NODE_ENV: 'test', API_URL: 'http://localhost:3000' }} />
* </head>
* ```
*/
export const EnvScript: FC<EnvScriptProps> = ({ env, nonce }) => {
let nonceString: string | undefined;
// XXX: Blocked by https://github.com/vercel/next.js/pull/58129
// if (typeof nonce === 'object' && nonce !== null) {
// // It's strongly recommended to set a nonce on your script tags.
// nonceString = headers().get(nonce.headerKey) ?? undefined;
// }
if (typeof nonce === 'string') {
nonceString = nonce;
}
return (
<Script
strategy="beforeInteractive"
nonce={nonceString}
dangerouslySetInnerHTML={{
__html: `window['__ENV'] = ${JSON.stringify(env)}`,
}}
/>
);
};
EnvScript
는 getPublicEnv으로 추출한 환경변수를 window 객체에 넣어주는 Next Script 이다.
정리해보면 PublicEnvScript
는 그냥 process.env
에 접근해서 환경변수를 뽑아내고 이걸 window 객체에 넣어주는 스크립트이다.
그래서 콘솔창에서 `window["__ENV"]를 입력하면 환경변수를 볼 수 있다.
그럼 이제 env
도 보자.
env
환경변수를 사용할 곳에서는 env
함수를 사용한다.
단순히 window["__ENV"]
를 리턴한다.
export function env(key: string): string | undefined {
if (isBrowser()) {
if (!key.startsWith('NEXT_PUBLIC_')) {
throw new Error(
`Environment variable '${key}' is not public and cannot be accessed in the browser.`,
);
}
return window["__ENV"][key];
}
noStore();
return process.env[key];
}
그럼 어떻게 Next.js에서는 접근하지 못하는 런타임 환경변수에 process.env로 접근한걸까??
next-runtime-env는 어떻게 런타임 환경변수를 읽는가?
Next.js의 클라이언트 컴포넌트는 런타임 환경변수를 읽을 수 없지만, 서버컴포넌트는 런타임 환경변수를 읽을 수 있다.
next-runtime-env는 서버에서 런타임 환경변수를 읽어 클라이언트에 window객체로 전달한다.
// 최상단 layout.tsx
export default function RootLayout({..}){
const publicEnv = getPublicEnv(); // 서버에서 런타임 환경변수 가져오기
return (
<html lang="en">
<head>
<Script strategy="beforeInteractive"
dangerouslySetInnerHTML={{__html:
`window['${PUBLIC_ENV_KEY}'] = ${JSON.stringify(env)}`,
}}/>
)}
그리고 env
함수에서는 window객체에 저장된 환경변수들을 빼온다.
return window['__ENV'][key];
요약
-
Next.js의 서버 컴포넌트는 런타임 환경변수에 접근할 수 있지만, 클라이언트 컴포넌트에서는 런타임 환경변수에 접근하지 못함.
-
next-runtime-env는 Next 서버가 실행될때 최상단
layout.tsx
에서 환경변수에 접근해서NEXT_PUBLIC_
으로 시작하는 환경변수들을 다 window 객체안에 넣는다. -
클라이언트 컴포넌트가 실행되면 window 객체안에서 런타임 환경변수들을 빼온다.