src 폴더 내의 파일들은 Vite가 빌드하면서 import.meta.env를 통해 환경변수를 착착 넣어주지만, public 폴더에 있는 정적 파일(Static Assets)들은 빌드 과정에서 코드 변환 없이 그대로 복사되어 .env를 사용할 수 없었습니다.
기존 코드 (public/firebase-messaging-sw.js):
//
firebase.initializeApp({
apiKey: "AIzaSyC5L9JsYGjBmBT0BU_VRe9cnXLng4lg80s",
projectId: "bandmate2-f562f",
// ...
});
이대로 깃허브에 올리면 악의적인 사용자가 내 할당량을 다 써버릴 수도 있다
"Vite의 빌드 프로세스에 개입(Hooking)"
단순히 파일을 복사하게 두지 말고, Custom Plugin을 만들어 빌드가 끝나는 시점에 파일 내용을 바꿔치기하는 전략
먼저 하드코딩된 키를 삭제하고, 나중에 채워 넣을 플레이스홀더(Placeholder)로 변경
수정된 public/firebase-messaging-sw.js:
firebase.initializeApp({
apiKey: "%VITE_FIREBASE_API_KEY%", // 👈 여기에 구멍을 뚫었습니다.
authDomain: "%VITE_FIREBASE_AUTH_DOMAIN%",
projectId: "%VITE_FIREBASE_PROJECT_ID%",
storageBucket: "%VITE_FIREBASE_STORAGE_BUCKET%",
messagingSenderId: "%VITE_FIREBASE_MESSAGING_SENDER_ID%",
appId: "%VITE_FIREBASE_APP_ID%"
});
vite.config.ts) 🛠️ReplaceEnvInSW 커스텀 플러그인을 사용했습니다.
import { defineConfig, loadEnv } from 'vite';
import fs from 'fs';
import path from 'path';
export default defineConfig(({ mode }) => {
// 1. 현재 모드(development/production)에 맞는 .env 파일을 로드합니다.
const env = loadEnv(mode, process.cwd(), '');
return {
plugins: [
react(),
VitePWA({ ... }),
// Magic Plugin ✨
{
name: 'replace-env-in-sw',
// closeBundle: 빌드가 완료되고 파일이 dist에 쓰여진 후 실행되는 훅
closeBundle() {
const swPath = path.resolve(__dirname, 'dist/firebase-messaging-sw.js');
if (fs.existsSync(swPath)) {
let swContent = fs.readFileSync(swPath, 'utf-8');
// 2. .env에서 로드한 값으로 플레이스홀더를 교체합니다.
swContent = swContent.replace(/%VITE_FIREBASE_API_KEY%/g, env.VITE_FIREBASE_API_KEY);
swContent = swContent.replace(/%VITE_FIREBASE_AUTH_DOMAIN%/g, env.VITE_FIREBASE_AUTH_DOMAIN);
swContent = swContent.replace(/%VITE_FIREBASE_PROJECT_ID%/g, env.VITE_FIREBASE_PROJECT_ID);
swContent = 0swContent.replace(/%VITE_FIREBASE_STORAGE_BUCKET%/g, env.VITE_FIREBASE_STORAGE_BUCKET);
swContent = swContent.replace(/%VITE_FIREBASE_MESSAGING_SENDER_ID%/g, env.VITE_FIREBASE_MESSAGING_SENDER_ID);
swContent = swContent.replace(/%VITE_FIREBASE_APP_ID%/g, env.VITE_FIREBASE_APP_ID);
fs.writeFileSync(swPath, swContent);
console.log('🔒 Firebase SW 환경변수 주입 완료!');
}
}
}
]
};
});
loadEnv: Vite가 제공하는 유틸리티로, .env 파일을 읽어 객체 형태로 가져옵니다.closeBundle Hook: Vite의 빌드 과정 중 "모든 번들링이 끝나고 파일이 생성된 직후"에 실행됩니다. 이때가 dist 폴더에 파일이 존재하는 시점입니다.fs (File System): Node.js의 파일 시스템 모듈을 이용해 dist/firebase-messaging-sw.js를 물리적으로 읽어서 내용을 수정하고 다시 저장합니다.이제 우리는 .env 파일에만 키를 보관하면 됩니다.
빌드할 때마다 Vite가 알아서 보안 처리된 Service Worker를 생성해줍니다
주의: 이 방법은
build명령어를 실행할 때 적용됩니다. 로컬 개발 서버(dev)에서는public폴더가 메모리 상에서 서빙되므로, 개발 환경을 위한 별도의 미들웨어를 추가하거나 로컬 테스트 시에는dist를 프리뷰하는 것이 좋습니다.
보안은 귀찮지만, 털리는 것보단 낫습니다 안전한 코딩 하세요! 🛡️