이롭게 현명하게

[Next.js] Next.js + Firebase(Firestore) 연동하기 + API 구조 설계 본문

웹 개발/Next.js

[Next.js] Next.js + Firebase(Firestore) 연동하기 + API 구조 설계

dev_y.h 2026. 5. 11. 18:10
728x90
반응형

Next.js와 Firebase

 


 

목차

 

Firebase란?

Firebase 프로젝트 생성하기

Firestore 데이터 생성하기

Next.js에서 Firebase 연동하기

Firestore 데이터 가져오기

API Route 구성하기(/api/board)

클라이언트에서 데이터 호출하기

전체 구조 흐름 정리

 


 


[Firebase란?]

Firebase

 

Firebase는 구글이 운영하는 백엔드 서비스로 애플리케이션에서 공통적으로 사용하는 백엔드 기능을 모아놓은 플랫폼이다.

웹, 앱 프로토타입이나 MVP 제품을 만들어야 하는 상황이라면 좋은 서비스다.

 

Firebase에는 Authentication이라는 서비스가 있다.

사용자 인증에 대한 서비스로 로그인, 회원가입, 비밀번호 찾기와 같은 인증과 관련된 서비스를 간단하게 개발할 수 있다.

사용자의 데이터를 저장하거나 조회할 수 있는 Firestore나 Realtime database 서비스가 있다.

이 서비스들은 NoSQL 기반으로 사용자의 데이터를 빠르게 저장할 수 있도록 지원한다.

 

Firebase의 큰 핵심은 빠르게 애플리케이션을 만들면서 출시하고 모니터링이 가능하다.

빠르게 시장 반응을 봐야 하는 스타트업에서 앱을 만들 때 앱에는 회원가입 기능이 있고 스타트업이 제공하는 핵심 기능이 있다.

사용자로서는 회원가입, 로그인, 계정 찾기 기능은 특별한 기능은 아니다.

하지만 백엔드 입장에서는 회원가입, 로그인, 계정 찾기와 같은 인증에 관한 서비스를 신규로 구축하고 유지보수하는 것은 까다로운 일이다.

프로젝트 스펙에 따라서 굉장한 비용이 소모되기 때문이다.

스타트업에서는 핵심 비즈니스 로직을 빨리 개발해서 프로토타입을 출시해야 하는데 이런 공통적인 기능만 만들다가 끝나버릴 수 있다.

이럴 때 파이어베이스 Authentication을 활용하면 간단히 해결할 수 있다.

 


 

처음에 Firebase를 접했을 때는 RDBMS와 같은 형태라고 생각했다.

하지만 실제로는 NoSQL 문서 중심 구조로 데이터를 조인해서 조회하는 방식이 지원되지 않아 많이 당황스러웠다.

그래서 UI/UX 흐름에 맞춰 데이터를 설계하는 데이터 모델링 과정이 매우 중요하다는 것을 알게 되었다.

 


[Firebase 프로젝트 생성하기]

Firebase에 로그인 후 Go to Console을 클릭한다.

로그인 후 Go to Console 클릭

 

Firebase 프로젝트 시작하기 클릭

 

프로젝트 이름 작성

프로젝트 이름 작성

 

저는 개발자 프로그램에 가입하기는 off 했습니다.

개발자 프로그램 가입하기 off

 

Gemini 사용 설정 화면이 나옵니다. 저는 사용하지 않았습니다.

gemini 사용 설정 off

 

Google 애널리틱스를 설정하는 화면이 나옵니다.

지금 당장은 필요하지 않다고 생각되어 설정하지 않았습니다.

프로젝트 만들기 클릭

 

기다리기

로딩 중

 

계속 클릭

 

프로젝트 개요 > 설정 > 프로젝트 설정

프로젝트 개요 > 설정

 

프로젝트 설정 클릭

 

프로젝트에 대한 정보가 나온다.

프로젝트 정보

 

저는 앱보다는 웹을 만들어보고 싶어 웹을 클릭했습니다.

웹 클릭

 

웹 앱의 이름 입력 > 앱 등록 클릭

Firebase에서 호스팅을 원하면 체크하면 됩니다.

저는 호스팅을 Vercel에서 할 예정이기 때문에 설정하지 않았습니다.

프로젝트 앱 등록
프로젝트 앱 등록

 

앱 등록을 클릭한 다음 화면에서 시크릿키가 나옵니다.

콘솔로 이동을 클릭해 줍니다.

시크릿 키
프로젝트 시크릿키

 

 

이제 Firebase 프로젝트를 생성했으니 실제로 데이터를 저장할 Firestore를 설정해 보겠습니다.

 

 

사이드바에서 모든 제품 클릭

모든 제품 클릭

 

cloud Firestore 클릭

 

데이터베이스 만들기 클릭

데이터베이스 만들기 클릭

 

데이터베이스 만들기에서 위치를 서울로 설정

(서버 위치는 서비스와 운영되는 곳과 가까우면 좋습니다.)

이때 위치를 설정한 후에는 수정이 불가능합니다.

데이터베이스 위치 설정
데이터베이스 위치 설정

 

저는 테스트 모드에서 시작을 했습니다.

테스트 모드에서 시작 > 만들기 클릭

테스트 모드에서 시작

 

만드는 중

 

컬렉션은 데이터를 그룹으로 묶는 단위입니다.

컬렉션 시작 클릭

컬렉션 시작 클릭

 

컬렉션 ID를 입력합니다.

간단한 테스트로 게시판 내용을 만들어볼 예정입니다.

그래서 저는 컬렉션 ID를 board로 하였습니다.

컬렉션 ID

 

문서 ID는 자동 ID를 클릭하였습니다.

board 컬렉션 문서 id

 

그리고 필드를 생성해 주었습니다.

문서 구조
문서 구조

 

저장 버튼을 클릭하면 다음과 같은 결과가 나옵니다.

 

Firebase에서 Firestore 데이터 설계가 완료되었습니다.

 


[Next.js에서 Firebase 연동하기]

[Firebase 공식문서 바로가기]

<연동단계>

  1. Firebase SDK 설치
  2. Firebase 앱 초기화
  3. Firestore 연결 설정

 

1. Firebase SDK 설치

Firebase 프로젝트 생성과 Firestore 데이터 설계까지 완료하였습니다.

지금 상태는 Firebase 콘솔에만 데이터가 존재할 뿐 Next.js 애플리케이션에서는 해당 데이터를 사용할 수 없습니다.

따라서 Firebase SDK를 설치하고 Next.js에서 Firestore에 접근할 수 있도록 연동이 필요합니다.

 

Firebase는 클라이언트에서도 직접 접근할 수 있지만, 보안을 고려하여 API Route를 통해 접근하는 구조로 설계했습니다.

 

npm install firebase

vscode 화면
명령어 실행

 

 

2. Firebase 앱 초기화

Firebase를 사용하려면 프로젝트 설정 정보를 기반으로 앱을 초기화해야 합니다.

발급받은 프로젝트 시크릿키를 설정해 줍니다.

src/data/firestore.ts

firestore.ts

 

3. Firestore연결

DB랑 연결해 주기 위해 코드를 추가해 줍니다.

firestore 연결

 

그리고 컬렉션의 모든 문서를 읽을 수 있도록 설정해주어야 합니다.

firestore.ts에 코드를 추가합니다.

import { collection, getDocs } from "firebase/firestore";

const querySnapshot = await getDocs(collection(db, "board"));
querySnapshot.forEach((doc) => {
    // doc.data() is never undefined for query doc snapshots
    console.log(doc.id, " => ", doc.data());
});

컬렉션 문서 읽기

 

비동기로 가져오기 위해 async를 사용해 줍니다.

import { initializeApp } from "firebase/app";
import { collection, getDocs, getFirestore } from "firebase/firestore";

// Your web app's Firebase configuration
const firebaseConfig = {
    apiKey: process.env.API_KEY,
    authDomain: process.env.AUTH_DOMAIN,
    projectId: process.env.PROJECT_ID,
    storageBucket: process.env.STORAGE_BUCKET,
    messagingSenderId: process.env.MESSAGING_SENDER_ID,
    appId: process.env.APP_ID,
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);

export async function fetchData() {
    const querySnapshot = await getDocs(collection(db, "cities"));
    querySnapshot.forEach((doc) => {
        // doc.data() is never undefined for query doc snapshots
        console.log(doc.id, " => ", doc.data());
    });
}

async 적용

<코드 설명>

  • const querySnapshot = await getDocs(collection(db, "cities")); : db에 "cities"라는 컬렉션을 가져온다.

 

저는 데이터베이스에 board라는 컬렉션을 만들어 둬서 board로 수정해 주었습니다.

querySnapshot은 Firestore에서 가져온 문서들의 집합이며 각 문서는 doc 객체로 접근할 수 있습니다.

 

 

<초기화 전체 코드>

import { initializeApp } from "firebase/app";
import { collection, getDocs, getFirestore } from "firebase/firestore";

// Your web app's Firebase configuration
const firebaseConfig = {
    apiKey: process.env.API_KEY,
    authDomain: process.env.AUTH_DOMAIN,
    projectId: process.env.PROJECT_ID,
    storageBucket: process.env.STORAGE_BUCKET,
    messagingSenderId: process.env.MESSAGING_SENDER_ID,
    appId: process.env.APP_ID,
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);

export async function fetchData() {
    const querySnapshot = await getDocs(collection(db, "board"));
    querySnapshot.forEach((doc) => {
        // doc.data() is never undefined for query doc snapshots
        console.log(doc.id, " => ", doc.data());
    });
}

초기설정 전체 코드

 

<모든 게시글 가져오기>

import { initializeApp } from "firebase/app";
import firebase from "firebase/compat/app";
import { collection, getDocs, getFirestore } from "firebase/firestore";

type BoardItem = {
    id: string;
    title: string;
    content: string;
    createAt: firebase.firestore.Timestamp;
};

// Your web app's Firebase configuration
const firebaseConfig = {
    apiKey: process.env.API_KEY,
    authDomain: process.env.AUTH_DOMAIN,
    projectId: process.env.PROJECT_ID,
    storageBucket: process.env.STORAGE_BUCKET,
    messagingSenderId: process.env.MESSAGING_SENDER_ID,
    appId: process.env.APP_ID,
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);

// 모든 게시글 가져오기
export async function fetchData() {
    const querySnapshot = await getDocs(collection(db, "board"));
    if (querySnapshot.empty) {
        return [];
    }
    const fetchArray: BoardItem[] = [];
    querySnapshot.forEach((doc) => {
        // doc.data() is never undefined for query doc snapshots
        console.log(doc.id, " => ", doc.data());
        const getData: BoardItem = {
            id: doc.id,
            title: doc.data()["title"],
            content: doc.data()["content"],
            createAt: doc.data()["createAt"].toDate(),
        };
        fetchArray.push(getData);
    });
    return fetchArray;
}

module.exports = {
    fetchData,
};

Firestore의 Timestamp 타입은 그대로 사용할 수 없기 때문에 JavaScript Date 객체로 변환해 주어야 합니다.

그래서. toDate()를 사용합니다.

 

 


[API Router 구성하기]

클라이언트가 Firebase에 직접 접근할 수 있지만 보안과 구조 분리 위해 서버(API Route)를 통해 데이터를 가져오는 방식으로 설계했습니다.

이 구조를 사용하면

  • 민감한 로직을 서버에 숨길 수 있다.
  • 권한 검증 로직 추가가 쉽다.
  • 유지보수와 확장이 용이다.

Firebase에서 데이터를 가져오는 fetchdata()를 호출하는 GET Method를 생성해 줍니다.

이때 데이터를 JSON 형태로 클라이언트에 반환합니다.

// src/app/api/board/route.ts
import { fetchData } from "@/data/firestore";
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
    try {
        const fetchedData = await fetchData();
        return NextResponse.json(
            { message: "데이터 가져오기 성공", data: fetchedData },
            { status: 200 }
        );
    } catch (error) {
        console.error("서버 에러:", error);
        return NextResponse.json({ message: "서버 에러", error: String(error) }, { status: 500 });
    }
}

 

localhost에서 확인하면 다음과 같이 나옵니다.

 


[클라이언트에서 데이터 호출하기]

클라이언트에서는 API를 호출하여 데이터를 가지고 옵니다.

/api/board API를 호출하여 데이터를 가져와야 합니다.

응답데이터를 state에 저장 후 UI에 렌더링 하는 방식으로 진행했습니다.

// src/app/board/page.tsx

"use client";

import { useEffect, useState } from "react";

type BoardItem = {
    id: string;
    title: string;
    content: string;
    createAt: string;
};

export default function Page() {
    const [boardData, setBoardData] = useState<BoardItem[]>([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        async function fetchBoard() {
            try {
                const response = await fetch("/api/board");
                const result = await response.json();
                setBoardData(result.data);
            } catch (error) {
                console.error("데이터 불러오기 실패", error);
            } finally {
                setLoading(false);
            }
        }
        fetchBoard();
    }, []);

    if (loading) {
        return <div>로딩중...</div>;
    }

    return (
        <div className="p-4">
            <h1 className="text-2xl font-bold mb-4">게시판 데이터</h1>
            <ul className="space-y-2">
                {boardData.map((item) => (
                    <li key={item.id} className="border p-2 rounded">
                        <h2 className="text-xl">{item.title}</h2>
                        <p>{item.content}</p>
                        <small>{item.createAt.toLocaleString()}</small>
                    </li>
                ))}
            </ul>
        </div>
    );
}

 

브라우저 화면에서 데이터가 잘 나오는 것을 확인할 수 있습니다.

브라우저에서 api 호출

 

 


[전체구조 흐름 정리]

Next.js + Firebase를 이용해서 게시판 데이터를 가져오는 구조이다.

Next.js와 Firebase의 구조

데이터베이스의 구조는 다음과 같다.

// board(Collection)
//  └─ {id} (Document)
//       ├─ id
//       ├─ title
//       ├─ content
//       └─ createAt

type BoardItem = {
    id: string;
    title: string;
    content: string;
    createAt: firebase.firestore.Timestamp;
};

 

  • Firestore(데이터베이스 계층)
    • firebase SDK를 사용해 Firebase에 연결
    • fetchData() : "board" 컬렉션에 데이터를 가져오는 함수 → board 컬렉션을 읽고 BoardItem[] 배열로 변환
  • Next.js API Route(서버 계층)
    • Next.js API Route
    • 클라이언트가 직접 Firebase에 접근하지 않고 /api/board라는 API를 만들어 fetchData를 호출
    • 가져온 데이터를 JSON 응답으로 반환
  • Next.js Page(클라이언트 계층)
    • /board 페이지에서 API(/api/board)를 호출해 데이터를 불러 옴
    • React 컴포넌트로 UI에 렌더링

 

<설계 구조>

설계 구조

  • 데이터베이스 : "board" 컬렉션 저장
  • 서버 : 클라이언트 요청을 받아 Firestore에서 데이터 가져오기
  • 클라이언트 : API 응답을 받아 UI에 렌더링

 

<코드 구성>

코드 구성

 

  • firestore.ts : Firestore연결& 데이터 fetch함수 정의
  • route.ts : API 엔드 포인트(/api/board)
  • page.tsx : 클라이언트에서 API 호출 & 화면 출력

 

<데이터 흐름>

  • /board 접속
  • 클라이언트 → /api/board API 호출
  • API → firestore에서 "board" 컬렉션 데이터 조회
  • Firestore →  API → 클라이언트(JSON) 응답
  • 클라이언트 UI 렌더링

 

<구현 특징>

  • 비동기 흐름 : fetch → API → Firestore의 데이터 요청 과정이 비동기적으로 이루어짐
  • 보안 : 클라이언트가 직접 Firestore에 접근하지 않고 API 경유
  • 타입 안전성 : BoardItem 타입으로 데이터 구조 관리
  • UX 고려 : 로딩 상태 표시
  • 확장성 : 추후 데이터 추가/수정 API 확장 가능

 


잘못된 정보는 댓글에 남겨주시면 감사하겠습니다!😊

댓글과 좋아요는 큰 힘이 됩니다!

 

더보기

ps) 내용을 어떻게 하면 더 잘 정리할 수 있을까..

 

 

728x90
반응형

'웹 개발 > Next.js' 카테고리의 다른 글

[Next.js] Next.js를 Vercel에 배포하기  (0) 2026.05.15
Comments