[STYNA] Firebase 구조 완전 정리 – firebase.ts 설계와 이유

주요 포인트 한눈에 보기

이 글은 완성된 포트폴리오 코드를 기준으로 Next.js + TypeScript + Firebase를 사용할 때
어떤 방식으로 제작되었는지 프로젝트를 분석합니다.
그 중 이번 챕터에서는 firebase.ts 초기화 구조를 분석합니다.

Auth, Firestore, Storage, Functions가 하나의 초기화 파일을 중심으로 어떻게 연결되고 사용되는지 흐름 단위로 정리합니다.

들어가기 앞서 + 전체 코드

이 문서는 Firebase 설정을 이미 완료한 상태에서,
단순 사용법이 아니라 작업이 완료된 포트폴리오에서 작성한 Firebase 코드가 왜 이런 구조인지
코드 기준으로 이해하고 싶은 사람을 위한 문서입니다.
따라서 Firebase의 모든 기능을 처음부터 설명하지 않고,
실제 프로젝트에 작성된 코드 흐름을 해부하며 기준을 정리하는 데 집중합니다.

이 글에서 다루지 않는 세부 기능이나 확장 내용은
각 섹션 하단에 첨부된 Firebase 공식 문서 링크를 참고하시면 됩니다.
이 글은 Firebase의 모든 기능을 설명하는 글이 아니며,
포트폴리오 코드 이해를 위한 길잡이 역할을 합니다.

[포트폴리오 링크]
[Git 코드 링크]

// src/shared/libs/firebase/firebase.ts [전체 코드]
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
import { getStorage } from "firebase/storage";
import { getFunctions, connectFunctionsEmulator } from "firebase/functions";

const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID
};

const app = initializeApp(firebaseConfig);

// Firebase Functions 초기화 - 지역 명시적 설정
const functions = getFunctions(app, 'us-central1');

// 개발 환경에서만 Functions 에뮬레이터 사용 (환경변수로 제어)
if (process.env.NODE_ENV === 'development' && process.env.NEXT_PUBLIC_USE_FIREBASE_EMULATOR === 'true') {
  try {
    connectFunctionsEmulator(functions, 'localhost', 5002);
    console.log('Connected to Functions emulator');
  } catch (error) {
    console.log('Functions emulator connection failed or already connected');
  }
}

export default app;
export const auth = getAuth(app);
export const db = getFirestore(app);
export const storage = getStorage(app);
export { functions };

Firebase 초기화 파일 구조 이해하기

Firebase는 애플리케이션 전체에서 단 한 번만 초기화되어야 하는 SDK입니다.
이 초기화 기준이 무너지면 중복 초기화 오류,
서버·클라이언트 실행 환경 충돌,
그리고 예측하기 어려운 버그로 이어질 수 있습니다.
그래서 Firebase 초기화는 컴포넌트나 페이지가 아닌
프로젝트 공통 진입점 역할을 하는 단일 파일로 분리해야 합니다.

Next.js + TypeScript 환경에서는 이 파일을 보통 firebase.ts 또는 lib/firebase.ts로 두고,
initializeApp은 이 파일에서만 호출합니다.
이후 Auth, Firestore, Storage, Functions는
모두 이 파일에서 생성된 app 인스턴스를 공유합니다.

Firebase 웹 SDK 기본 설정에 대한 전체 흐름은
공식 문서를 참고하시기 바랍니다.

[Firebase 공식 문서 링크]

이제 포트폴리오에서 사용된 코드 설명을 드리겠습니다.
흐름의 이해를 쉽게 하기 위해, 4단계로 나눠서 설명드리겠습니다.

// src/shared/libs/firebase/firebase.ts
      const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
};

// .env  
NEXT_PUBLIC_FIREBASE_API_KEY=환경변수
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=환경변수
NEXT_PUBLIC_FIREBASE_PROJECT_ID=환경변수
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=환경변수
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=환경변수
NEXT_PUBLIC_FIREBASE_APP_ID=환경변수

첫 번째 단계는 환경 변수 분리입니다.
Firebase 설정 값은 절대 코드에 직접 하드코딩하지 않고,
Next.js의 환경 변수 시스템을 통해 주입합니다.
이렇게 하면 배포 환경과 로컬 환경을 동일한 코드로 운영할 수 있습니다.

const app = initializeApp(firebaseConfig);

두 번째 단계는 초기화 파일 단일화입니다.
initializeApp은 반드시 이 파일에서만 실행되어야 하며,
다른 어떤 컴포넌트나 훅에서도 다시 호출하지 않습니다.
이후 모든 Firebase 기능은 이 app 인스턴스를 공유합니다.

import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
import { getStorage } from "firebase/storage";
import { getFunctions, connectFunctionsEmulator } from "firebase/functions";
      
export const auth = getAuth(app);
export const db = getFirestore(app);
export const storage = getStorage(app);
export const functions = getFunctions(app);

세 번째 단계는 기능별 인스턴스 export입니다.
auth, db, storage, functions를 각각 export 해두면,
사용하는 쪽에서는 “가져다 쓰기”만 하면 되므로
Firebase 내부 구조를 몰라도 개발이 가능합니다.

// 개발 환경에서만 Functions 에뮬레이터 사용 (환경변수로 제어)
if (process.env.NODE_ENV === 'development' && process.env.NEXT_PUBLIC_USE_FIREBASE_EMULATOR === 'true') {
  try {
    connectFunctionsEmulator(functions, 'localhost', 5002);
    console.log('Connected to Functions emulator');
  } catch (error) {
    console.log('Functions emulator connection failed or already connected');
  }
}

네 번째 단계는 개발 환경과 운영 환경 분리입니다.
Functions 에뮬레이터처럼 개발 전용 설정은
반드시 환경 변수로 분기 처리해야 하며,
운영 환경에서는 절대 실행되지 않도록 합니다.

이 코드는 언제 에뮬레이터를 연결할지를 조건으로 명확하게 제한합니다.
process.env.NODE_ENV === 'development' 조건은
현재 실행 환경이 로컬 개발 환경인지 여부를 판단합니다.
즉, 실제 서비스로 배포된 환경에서는 이 블록 자체가 실행되지 않습니다.

두 번째 조건인 NEXT_PUBLIC_USE_FIREBASE_EMULATOR
개발 환경 안에서도 의도적으로 에뮬레이터를 사용할 때만
연결되도록 제어하기 위한 스위치 역할을 합니다.
이 값이 없다면, 개발 중에도 실수로 실제 Functions를 호출할 수 있습니다.

connectFunctionsEmulator
Firebase Functions 호출 대상을
실제 서버가 아닌 내 컴퓨터(localhost)로 변경합니다.
이 예제에서는 5002 포트를 사용하며,
이는 Firebase Emulator 실행 시 설정한 포트와 일치해야 합니다.

try-catch로 감싸는 이유는,
Next.js 개발 환경에서 파일이 여러 번 실행되거나
이미 에뮬레이터에 연결된 상태에서
다시 연결을 시도할 수 있기 때문입니다.
이 예외 처리를 통해 개발 중 불필요한 에러 로그를 방지합니다.

Auth, Firestore, Storage, Functions 각각의 사용 방법과 옵션은 다른 글에서 다룰 예정입니다.
다만, 모든 내용을 다루지 않고 반드시 알아야 할 내용만 작성할 예정입니다.
만약 더 다양한 내용을 학습하고 싶으시면 아래 공식 문서를 기준으로 학습하시면 됩니다.

[Auth 공식 문서 링크]
[Firestore 공식 문서 링크]
[Storage 공식 문서 링크]
[Functions 공식 문서 링크]

자주 발생하는 실수 및 주의사항

가장 흔한 실수는 firebase.ts 파일을 만들어두고도
다른 파일에서 initializeApp을 다시 호출하는 경우입니다.
또한 서버 컴포넌트에서 Firebase 클라이언트 SDK를 직접 사용하는 것도
Next.js 환경에서는 오류의 원인이 됩니다.

잘못된 구조 문제점
컴포넌트 내부 초기화 중복 초기화 및 환경 충돌
서버 컴포넌트에서 사용 브라우저 API 접근 불가

정리 및 다음 편 예고

이 문서에서는 Firebase 초기화 파일을 기준으로
프로젝트 전체 구조를 어떻게 안정적으로 설계하는지 살펴봤습니다.
다음 편에서는 아래 코드에서 사용되는 auth 객체를 중심으로,
Firebase 인증 로직을 하나씩 해부하며 학습할 예정입니다.

다음 글에서는 STYNA 포트폴리오에 실제 적용된 Firebase Authentication 코드를 기준으로,
로그인 유지, 로그아웃, 비밀번호 재설정이
어떤 흐름으로 연결되어 동작하는지를 간단하고 명확하게 설명합니다.
모든 옵션을 나열하기보다,
실제 서비스에서 반드시 필요한 인증 흐름에만 집중합니다.

[Firebase Authentication]

import { auth } from "./firebase";
import {
  browserSessionPersistence,
  createUserWithEmailAndPassword,
  setPersistence,
  signInWithEmailAndPassword,
  signOut,
  sendPasswordResetEmail,
  confirmPasswordReset,
  verifyPasswordResetCode
} from "firebase/auth";

FAQ

Q. 이 문서만 보면 Firebase를 다 이해할 수 있나요?
아닙니다. 이 문서는 포트폴리오 코드 구조 이해를 목표로 하며,
세부 기능은 공식 문서를 함께 참고해야 합니다.

Q. 이 구조는 실무에서도 사용 가능한가요?
네. Firebase 초기화 기준을 분리하고
단일 app 인스턴스를 공유하는 구조는
실무에서도 널리 사용되는 패턴입니다.

이 글이 마음에 드세요?

RSS 피드를 구독하세요!

댓글 남기기