빌드 없이 환경별 값을 바꾸고 싶다면, 설정을 번들 밖으로 분리해 런타임에 불러오면 됩니다. API 베이스 URL, 기능 플래그, 서드파티 키 등은 동적 로드가 적합합니다. 실서비스에서 안전하고 단순하게 적용할 수 있는 두 가지 패턴과 에러/캐시/배포 전략을 정리합니다.
1. 목표와 원칙
목표는 재빌드 없이 설정을 교체하고, 초기 렌더 전에 설정을 보장하는 것입니다. 원칙은 다음과 같습니다. 설정은 퍼블릭 정보만 포함합니다(비밀 키 금지), 앱 시작 전에 1회 로드, 실패 시 명확한 폴백 UI 제공, 캐시 정책을 통해 변경 전파를 제어합니다.
2. 접근 방식 개요
방식 A: public/config.json을 fetch로 가져온 뒤 React를 마운트합니다. 단순하고 어디서나 동작합니다.
방식 B: public/config.js에서 window 전역에 구성 객체를 주입하고, index.html에서 번들 전에 로드합니다. 네트워크 1회 호출이며 CDN 캐시 전략이 유연합니다.
3. 구현 A: config.json을 fetch 후 부트스트랩
1) public/config.json 작성
{
"API_BASE_URL": "https://api.example.com",
"FEATURE_FLAGS": { "newDashboard": true },
"SENTRY_DSN": ""
}
2) src/config.js: 로드/접근 유틸
// src/config.js
export async function loadConfig(path = '/config.json', { timeout = 5000 } = {}) {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
const res = await fetch(path, { signal: controller.signal, cache: 'no-cache' });
clearTimeout(id);
if (!res.ok) throw new Error('Failed to load config');
const json = await res.json();
window.__APP_CONFIG__ = json;
return json;
}
export function getConfig() {
return window.__APP_CONFIG__ || {};
}
3) src/ConfigContext.js: 컨텍스트로 주입
// src/ConfigContext.js
import React, { createContext, useContext } from 'react';
import { getConfig } from './config';
const ConfigContext = createContext(getConfig());
export function ConfigProvider({ children }) {
return (
<ConfigContext.Provider value={getConfig()}>
{children}
</ConfigContext.Provider>
);
}
export function useConfig() {
return useContext(ConfigContext);
}
4) src/main.jsx: 설정을 먼저 로드하고 렌더
// src/main.jsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
import { loadConfig } from './config';
import { ConfigProvider } from './ConfigContext';
function render() {
const container = document.getElementById('root');
const root = createRoot(container);
root.render(
<ConfigProvider>
<App />
</ConfigProvider>
);
}
(async () => {
try {
// 호스트네임에 따라 경로 분기 예시
const map = {
'localhost': '/config.local.json',
'staging.example.com': '/config.staging.json',
'app.example.com': '/config.json'
};
const path = map[location.hostname] || '/config.json';
await loadConfig(`${path}?v=${import.meta?.env?.VITE_BUILD_ID || Date.now()}`);
render();
} catch (e) {
console.error('Config load failed', e);
const el = document.getElementById('root');
el.innerHTML = '구성을 불러오지 못했습니다. 새로고침하거나 잠시 후 다시 시도해주세요.
';
}
})();
5) 컴포넌트에서 사용
// 예: src/App.jsx
import React from 'react';
import { useConfig } from './ConfigContext';
export default function App() {
const cfg = useConfig();
return (
<div>
<h1>API BASE: {cfg.API_BASE_URL}</h1>
</div>
);
}
4. 구현 B: config.js 전역 주입 패턴
1) public/config.js 작성
// public/config.js
window.__APP_CONFIG__ = {
API_BASE_URL: 'https://api.example.com',
FEATURE_FLAGS: { newDashboard: true },
SENTRY_DSN: ''
};
2) public/index.html에서 번들 전에 삽입
<!-- public/index.html -->
<script src="/config.js"></script>
<script type="module" src="/src/main.jsx"></script>
3) 앱에서는 getConfig()로 그대로 사용합니다. 이 방식은 네트워크 추가 fetch가 없고, CI/CD에서 config.js만 교체 배포하기 쉽습니다.
5. 에러 처리와 폴백
설정 로드 실패 시 사용자에게 명확한 메시지를 보여주고, 재시도 버튼이나 자동 재시도(지수 백오프)를 고려합니다. 일부 값만 필요한 경우 안전한 기본값을 두되, API 베이스 URL처럼 핵심 설정은 없으면 앱을 멈추는 것이 낫습니다.
6. 캐시와 배포 전략
config.json은 변경 전파를 위해 Cache-Control: no-store 또는 짧은 max-age를 권장합니다. 빌드 ID/커밋 해시를 쿼리스트링으로 붙여 강제 갱신할 수 있습니다. CDN을 쓴다면 stale-while-revalidate로 체감 성능을 유지하면서 빠르게 갱신할 수 있습니다. config.js 패턴은 파일명에 해시를 붙이거나 심플하게 동일 경로에 덮어써도 됩니다.
7. 보안 체크리스트
민감한 비밀은 절대 프런트 설정에 넣지 않습니다. 별도 도메인에서 설정을 제공한다면 CORS와 HTTPS를 설정합니다. CSP를 사용하는 경우 config.js에 대한 스크립트 소스 허용을 추가합니다. 도메인 변조를 방지하려면 설정에 허용된 오리진 목록을 넣고, 런타임에 검증할 수 있습니다.
8. 운영 팁
모니터링 도입: 설정 로드 실패율, 지연 시간, 로드된 설정 버전을 로깅합니다. 기능 플래그는 기본값과 만료일을 명시하고 주기적으로 정리합니다. 구성 스키마를 정의하고 런타임 검증(예: 필수 키 누락 시 오류)을 추가하면 디버깅 시간을 줄일 수 있습니다.
// 간단한 스키마 검증 예시
export function assertConfigShape(cfg) {
const required = ['API_BASE_URL'];
required.forEach((k) => {
if (!cfg[k]) throw new Error(`Missing config: ${k}`);
});
}
9. SSR/Next.js 메모
Next.js 등 SSR에서는 서버 환경변수를 서버에서 읽어 HTML에 주입하거나, next/config의 runtime config를 사용합니다. 순수 CSR(Vite/CRA)에서는 본문처럼 외부 파일 로드가 단순하고 효과적입니다.
10. 결론
동적 구성 로드는 재빌드 없이 환경을 전환하고, 배포 파이프라인을 단순화합니다. 소규모는 config.json fetch, 대규모/멀티리전은 config.js 주입과 CDN 캐시 전략을 권장합니다. 초기에 부트스트랩을 정리해두면 운영 비용이 크게 줄어듭니다.
'React' 카테고리의 다른 글
| React에서 컴포넌트 렌더링 조건 최적화하기 (0) | 2026.07.12 |
|---|---|
| React 앱에서 스마트폰 센서 데이터 통합 처리 (0) | 2026.07.12 |
| React 앱에서 사용자 활동 로그 시각화하기 (1) | 2026.07.10 |
| React에서 브라우저 Speech Synthesis API 활용하여 음성 출력하기 (0) | 2026.07.09 |
| React 앱에서 블루투스 디바이스 연동하기 (0) | 2026.07.09 |