Firebase를 Next.js 앱과 통합

1. 시작하기 전에

이 Codelab에서는 Firebase를 레스토랑 리뷰 웹사이트인 Friendly Eats라는 Next.js 웹 앱과 통합하는 방법을 알아봅니다.

Friendly Eats 웹 앱

완성된 웹 앱은 Next.js 앱을 빌드하는 데 Firebase가 어떻게 도움이 되는지 보여주는 유용한 기능을 제공합니다. 이러한 기능에는 다음과 같은 사항이 포함됩니다.

  • Google 계정으로 로그인 및 로그아웃 기능: 완성된 웹 앱을 사용하면 Google 계정으로 로그인하고 로그아웃할 수 있습니다. 사용자 로그인 및 지속성은 전적으로 Firebase 인증을 통해 관리됩니다.
  • 이미지: 로그인 사용자는 완성된 웹 앱을 통해 레스토랑 이미지를 업로드할 수 있습니다. 이미지 애셋은 Firebase용 Cloud Storage에 저장됩니다. Firebase JavaScript SDK는 업로드된 이미지에 공개 URL을 제공합니다. 이 공개 URL은 Cloud Firestore의 관련 레스토랑 문서에 저장됩니다.
  • 리뷰: 완성된 웹 앱을 통해 로그인한 사용자가 별표 평점과 텍스트 기반 메시지로 구성된 레스토랑 리뷰를 게시할 수 있습니다. 리뷰 정보는 Cloud Firestore에 저장됩니다.
  • 필터: 완성된 웹 앱을 통해 로그인한 사용자는 카테고리, 위치, 가격에 따라 레스토랑 목록을 필터링할 수 있습니다. 사용되는 정렬 방법을 맞춤설정할 수도 있습니다. Cloud Firestore에서 데이터에 액세스하며 Firestore 쿼리는 사용된 필터를 기준으로 적용됩니다.

기본 요건

  • Next.js 및 JavaScript에 관한 지식

학습할 내용

  • Next.js 앱 라우터 및 서버 측 렌더링과 함께 Firebase를 사용하는 방법
  • Firebase용 Cloud Storage에서 이미지를 유지하는 방법
  • Cloud Firestore 데이터베이스에서 데이터를 읽고 쓰는 방법
  • Firebase JavaScript SDK로 Google 계정으로 로그인을 사용하는 방법

필요한 사항

  • Git
  • Java 개발 키트
  • Node.js의 최신 안정화 버전
  • 원하는 브라우저(예: Chrome)
  • 코드 편집기와 터미널이 있는 개발 환경
  • Firebase 프로젝트 생성 및 관리를 위한 Google 계정
  • Firebase 프로젝트를 Blaze 요금제로 업그레이드할 수 있음

2. 개발 환경 설정

이 Codelab은 앱의 시작 코드베이스를 제공하며 Firebase CLI를 사용합니다.

저장소 다운로드

  1. 터미널에서 Codelab의 GitHub 저장소를 클론합니다.
    git clone https://github.com/firebase/friendlyeats-web.git
    
  2. GitHub 저장소에는 여러 플랫폼용 샘플 프로젝트가 있습니다. 그러나 이 Codelab에서는 nextjs-start 디렉터리만 사용합니다. 다음 디렉터리를 기록해 둡니다.
    • nextjs-start: 빌드의 기반이 되는 시작 코드가 있습니다.
    • nextjs-end: 완료된 웹 앱의 솔루션 코드를 포함합니다.
  3. 터미널에서 nextjs-start 디렉터리로 이동하여 필요한 종속 항목을 설치합니다.
    cd friendlyeats-web/nextjs-start
    npm install
    

Firebase CLI 설치 또는 업데이트

다음 명령어를 실행하여 Firebase CLI가 설치되어 있고 v12.5.4 이상인지 확인합니다.

firebase --version
  • Firebase CLI가 설치되어 있지만 v12.5.4 이상이 아닌 경우 업데이트합니다.
    npm update -g firebase-tools
    
  • Firebase CLI가 설치되어 있지 않으면 설치합니다.
    npm install -g firebase-tools
    

권한 오류로 인해 Firebase CLI를 설치할 수 없는 경우 npm 문서를 참조하거나 다른 설치 옵션을 사용하세요.

Firebase에 로그인

  1. 다음 명령어를 실행하여 Firebase CLI에 로그인합니다.
    firebase login
    
  2. Firebase에서 데이터를 수집할지 여부에 따라 Y 또는 N을 입력합니다.
  3. 브라우저에서 Google 계정을 선택한 다음 허용을 클릭합니다.

셋째, Firebase 프로젝트 설정

이 섹션에서는 Firebase 프로젝트를 설정하고 Firebase 웹 앱을 연결합니다. 샘플 웹 앱에서 사용하는 Firebase 서비스도 설정합니다.

Firebase 프로젝트 만들기

  1. Firebase Console에서 프로젝트 만들기를 클릭합니다.
  2. 프로젝트 이름 입력 텍스트 상자에 FriendlyEats Codelab(또는 원하는 프로젝트 이름)을 입력한 후 계속을 클릭합니다.
  3. 이 Codelab에서는 Google 애널리틱스가 필요하지 않으므로 이 프로젝트에 Google 애널리틱스 사용 설정 옵션을 사용 중지합니다.
  4. 프로젝트 만들기를 클릭합니다.
  5. 프로젝트가 프로비저닝될 때까지 기다린 후 계속을 클릭합니다.
  6. Firebase 프로젝트에서 프로젝트 설정으로 이동합니다. 나중에 필요하므로 프로젝트 ID를 기록해 둡니다. 이 고유 식별자는 프로젝트를 식별하는 방법입니다(예: Firebase CLI에서).

Firebase 프로젝트에 웹 앱 추가

  1. Firebase 프로젝트에서 프로젝트 개요로 이동한 후 e41f2efdd9539c31.png 을 클릭합니다.
  2. 앱 닉네임 텍스트 상자에 My Next.js app과 같이 기억하기 쉬운 앱 닉네임을 입력합니다.
  3. 이 앱에 Firebase 호스팅도 설정 체크박스를 선택합니다.
  4. 앱 등록 > 다음 > 다음 > 콘솔로 이동을 클릭합니다.

Firebase 요금제 업그레이드

웹 프레임워크를 사용하려면 Firebase 프로젝트에 Blaze 요금제를 사용해야 합니다. 즉, Cloud Billing 계정에 연결되어 있어야 합니다.

  • Cloud Billing 계정에는 신용카드와 같은 결제 수단이 필요합니다.
  • Firebase와 Google Cloud를 처음 사용하는 경우 $300 크레딧과 무료 체험판 Cloud Billing 계정을 받을 자격이 되는지 확인하세요.

그러나 이 Codelab을 완료해도 실제로 요금이 청구되지는 않습니다.

프로젝트를 Blaze 요금제로 업그레이드하려면 다음 단계를 따르세요.

  1. Firebase Console에서 요금제 업그레이드를 선택합니다.
  2. 대화상자에서 Blaze 요금제를 선택한 다음 화면에 표시된 안내에 따라 프로젝트를 Cloud Billing 계정과 연결합니다.
    Cloud Billing 계정을 만들어야 하는 경우 업그레이드를 완료하기 위해 Firebase Console의 업그레이드 흐름으로 돌아가야 할 수 있습니다.

Firebase Console에서 Firebase 서비스 설정

인증 설정

  1. Firebase Console에서 인증으로 이동합니다.
  2. 시작하기를 클릭합니다.
  3. 추가 제공업체 열에서 Google > 사용 설정을 클릭합니다.
  4. 프로젝트의 공개용 이름 텍스트 상자에 기억하기 쉬운 이름(예: My Next.js app)을 입력합니다.
  5. 프로젝트 지원 이메일 드롭다운에서 이메일 주소를 선택합니다.
  6. 저장을 클릭합니다.

Cloud Firestore 설정

  1. Firebase Console에서 Firestore로 이동합니다.
  2. 데이터베이스 만들기 > 테스트 모드에서 시작 > 다음을 클릭합니다.
    이 Codelab의 후반부에서 데이터를 보호하는 보안 규칙을 추가합니다. 데이터베이스에 대한 보안 규칙을 추가하지 않은 채 앱을 공개적으로 배포하거나 노출하지 마세요.
  3. 기본 위치를 사용하거나 원하는 위치를 선택합니다.
    실제 앱에서는 사용자와 가까운 위치를 선택하는 것이 좋습니다. 이 위치는 나중에 변경할 수 없으며 자동으로 기본 Cloud Storage 버킷의 위치가 됩니다(다음 단계).
  4. 완료를 클릭합니다.

Firebase용 Cloud Storage 설정

  1. Firebase Console에서 스토리지로 이동합니다.
  2. 시작하기 > 테스트 모드에서 시작 > 다음을 클릭합니다.
    이 Codelab의 후반부에서 데이터를 보호하는 보안 규칙을 추가합니다. 스토리지 버킷에 대한 보안 규칙을 추가하지 않은 채 앱을 공개적으로 배포하거나 노출하지 마세요.
  3. 이전 단계에서 Firestore를 설정했으므로 버킷의 위치는 이미 선택되어 있습니다.
  4. 완료를 클릭합니다.

4. 시작 코드베이스 검토

이 섹션에서는 이 Codelab에서 기능을 추가할 앱의 시작 코드베이스의 몇 가지 영역을 검토합니다.

폴더 및 파일 구조

다음 표에는 앱의 폴더 및 파일 구조에 대한 개요가 포함되어 있습니다.

폴더 및 파일

설명

src/components

필터, 헤더, 레스토랑 세부정보, 리뷰에 대한 React 구성요소

src/lib

React 또는 Next.js에 바인딩되지 않은 유틸리티 함수

src/lib/firebase

Firebase 관련 코드 및 Firebase 구성

public

웹 앱의 정적 애셋(예: 아이콘)

src/app

Next.js 앱 라우터를 사용한 라우팅

src/app/restaurant

API 경로 핸들러

package.jsonpackage-lock.json

npm을 사용한 프로젝트 종속 항목

next.config.js

Next.js 관련 구성(서버 작업이 사용 설정됨)

jsconfig.json

JavaScript 언어 서비스 구성

서버 및 클라이언트 구성요소

이 앱은 앱 라우터를 사용하는 Next.js 웹 앱입니다. 서버 렌더링은 앱 전체에서 사용됩니다. 예를 들어 src/app/page.js 파일은 기본 페이지를 담당하는 서버 구성요소입니다. src/components/RestaurantListings.jsx 파일은 파일 시작 부분에 "use client" 지시어로 표시된 클라이언트 구성요소입니다.

Import 문

import 문은 다음과 같습니다.

import RatingPicker from "@/src/components/RatingPicker.jsx";

앱은 투박한 상대 가져오기 경로를 피하기 위해 @ 기호를 사용하며, 경로 별칭을 사용할 수 있습니다.

Firebase 관련 API

모든 Firebase API 코드는 src/lib/firebase 디렉터리에 래핑됩니다. 그러면 개별 React 구성요소는 Firebase 함수를 직접 가져오는 대신 src/lib/firebase 디렉터리에서 래핑된 함수를 가져옵니다.

모의 데이터

모의 레스토랑 및 리뷰 데이터는 src/lib/randomData.js 파일에 포함되어 있습니다. 이 파일의 데이터는 src/lib/fakeRestaurants.js 파일의 코드에 조합됩니다.

다섯째, Firebase 호스팅 에뮬레이터로 로컬 호스팅 설정

이 섹션에서는 Firebase 호스팅 에뮬레이터를 사용하여 Next.js 웹 앱을 로컬에서 실행합니다.

이 섹션을 마치면 Firebase 호스팅 에뮬레이터가 Next.js 앱을 자동으로 실행하므로 에뮬레이터와 별도의 프로세스에서 Next.js를 실행할 필요가 없습니다.

Firebase 서비스 계정 다운로드 및 사용

이 Codelab에서 빌드할 웹 앱은 Next.js를 통한 서버 측 렌더링을 사용합니다.

Node.js용 Firebase Admin SDK는 서버 측 코드에서 보안 규칙이 작동하는지 확인하는 데 사용됩니다. Firebase Admin에서 API를 사용하려면 Firebase Console에서 Firebase 서비스 계정을 다운로드하여 사용해야 합니다.

  1. Firebase Console에서 프로젝트 설정서비스 계정 페이지로 이동합니다.
  2. 새 비공개 키 생성 > 키 생성을 클릭합니다.
  3. 파일이 파일 시스템에 다운로드되면 파일의 전체 경로를 확인합니다.
    예를 들어 파일을 Downloads 디렉터리에 다운로드한 경우 전체 경로는 다음과 같습니다./Users/me/Downloads/my-project-id-firebase-adminsdk-123.json
  4. 터미널에서 GOOGLE_APPLICATION_CREDENTIALS 환경 변수를 다운로드한 비공개 키의 경로로 설정합니다. Unix 환경에서는 명령어가 다음과 같을 수 있습니다.
    export GOOGLE_APPLICATION_CREDENTIALS="/Users/me/Downloads/my-project-id-firebase-adminsdk-123.json"
    
  5. 새 터미널 세션을 시작하면 환경 변수가 손실될 수 있으므로 이 터미널을 열어 두고 이 Codelab의 나머지 부분에서 사용합니다.
    새 터미널 세션을 열면 이전 명령어를 다시 실행해야 합니다.

웹 앱 코드에 Firebase 구성 추가

  1. Firebase Console에서 프로젝트 설정으로 이동합니다.
  2. SDK 설정 및 구성 창에서 firebaseConfig 변수를 찾아 속성과 값을 복사합니다.
  3. 코드 편집기에서 .env 파일을 열고 Firebase Console의 구성 값으로 환경 변수 값을 입력합니다.
  4. 파일에서 기존 속성을 복사한 속성으로 바꿉니다.
  5. 파일을 저장합니다.

Firebase 프로젝트로 웹 앱 초기화

웹 앱을 Firebase 프로젝트에 연결하려면 다음 단계를 따르세요.

  1. 터미널에서 Firebase의 웹 프레임워크가 사용 설정되어 있는지 확인합니다.
    firebase experiments:enable webframeworks
    
  2. Firebase를 초기화합니다.
    firebase init
    
  3. 다음 옵션을 선택합니다.
    • Firestore: Firestore용 보안 규칙 및 색인 파일 구성
    • 호스팅: Firebase 호스팅용 파일을 구성하고 원하는 경우 GitHub 작업 배포 설정
    • 스토리지: Cloud Storage용 보안 규칙 파일 구성
    • 에뮬레이터: Firebase 제품용 로컬 에뮬레이터 설정
  4. 기존 프로젝트 사용을 선택하고 앞에서 기록한 프로젝트 ID를 입력합니다.
  5. 해당하는 경우 서버 측 콘텐츠를 호스팅하려는 리전은 어디인가요?라는 질문에 도달할 때까지 이후의 모든 질문에 대해 기본값을 선택합니다. 터미널에 현재 디렉터리에서 기존 Next.js 코드베이스가 감지되었다는 메시지가 표시됩니다.
  6. 해당하는 경우 서버 측 콘텐츠를 호스팅하려는 리전은 어디인가요?라는 질문에서 이전에 Firestore 및 Cloud Storage로 선택한 위치를 선택합니다.
  7. 어떤 Firebase 에뮬레이터를 설정하시겠어요?라는 질문에 도달할 때까지 이후 모든 질문의 기본값을 선택합니다. 이 질문의 경우 Functions 에뮬레이터호스팅 에뮬레이터를 선택합니다.
  8. 다른 모든 질문에는 기본값을 선택합니다.

보안 규칙 배포

코드에는 이미 Firestore 및 Firebase용 Cloud Storage에 대한 보안 규칙 세트가 있습니다. 보안 규칙을 배포하면 데이터베이스와 버킷의 데이터를 오용으로부터 더욱 안전하게 보호할 수 있습니다.

  1. 이러한 보안 규칙을 배포하려면 터미널에서 다음 명령어를 실행합니다.
    firebase deploy --only firestore:rules,storage
    
  2. "Cloud Storage for Firebase needs an IAM Role to use cross-service rules. Grant the new role?"이라는 메시지가 표시되면 를 선택합니다.

호스팅 에뮬레이터 시작

  1. 터미널에서 호스팅 에뮬레이터를 시작합니다.
    firebase emulators:start --only hosting
    
    터미널은 호스팅 에뮬레이터를 찾을 수 있는 포트(예: http://localhost:5000/)로 응답합니다.

호스팅 에뮬레이터가 준비되었음을 보여주는 터미널

  1. 브라우저에서 Firebase 호스팅 에뮬레이터가 있는 URL로 이동합니다.
  2. 웹페이지에 "Error: Firebase session cookie has incorrect..."와 같이 시작하는 오류가 표시되면 localhost 환경에서 모든 쿠키를 삭제해야 합니다. 이렇게 하려면 쿠키 삭제 | DevTools 문서를 참조하세요.

쿠키 세션 오류

DevTools에서 쿠키 삭제

이제 초기 웹 앱을 볼 수 있습니다. 웹 앱은 localhost URL로 표시되지만 콘솔에서 구성한 실제 Firebase 서비스를 사용합니다.

6. 웹 앱에 인증 추가

이 섹션에서는 로그인할 수 있도록 웹 앱에 인증을 추가합니다.

로그인 및 로그아웃 함수 구현

  1. src/lib/firebase/auth.js 파일에서 onAuthStateChanged, signInWithGoogle, signOut 함수를 다음 코드로 바꿉니다.
export function onAuthStateChanged(cb) {
        return _onAuthStateChanged(auth, cb);
}

export async function signInWithGoogle() {
        const provider = new GoogleAuthProvider();

        try {
                await signInWithPopup(auth, provider);
        } catch (error) {
                console.error("Error signing in with Google", error);
        }
}

export async function signOut() {
        try {
                return auth.signOut();
        } catch (error) {
                console.error("Error signing out with Google", error);
        }
}

이 코드는 다음과 같은 Firebase API를 사용합니다.

Firebase API

설명

GoogleAuthProvider

Google 인증 제공업체 인스턴스를 만듭니다.

signInWithPopup

대화상자 기반 인증 흐름을 시작합니다.

auth.signOut

사용자를 로그아웃시킵니다.

src/components/Header.jsx 파일에서 코드는 이미 signInWithGooglesignOut 함수를 호출합니다.

  1. 웹 앱에서 페이지를 새로고침하고 Google 계정으로 로그인을 클릭합니다. 웹 앱이 업데이트되지 않으므로 로그인이 성공했는지 알 수 없습니다.

인증 변경사항 구독

인증 변경사항을 구독하려면 다음 단계를 따르세요.

  1. src/components/Header.jsx 파일로 이동합니다.
  2. useUserSession 함수를 코드를 다음 코드로 바꿉니다.
function useUserSession(initialUser) {
        // The initialUser comes from the server through a server component
        const [user, setUser] = useState(initialUser);
        const router = useRouter();

        useEffect(() => {
                const unsubscribe = onAuthStateChanged(authUser => {
                        setUser(authUser);
                });
                return () => {
                        unsubscribe();
                };
        }, []);

        useEffect(() => {
                onAuthStateChanged(authUser => {
                        if (user === undefined) return;
                        if (user?.email !== authUser?.email) {
                                router.refresh();
                        }
                });
        }, [user]);

        return user;
}

이 코드는 onAuthStateChanged 함수가 인증 상태에 변경이 있다고 지정할 때 React state 후크를 사용하여 사용자를 업데이트합니다.

변경사항 확인

src/app/layout.js 파일의 루트 레이아웃은 헤더를 렌더링하고 가능한 경우 사용자를 속성으로 전달합니다.

<Header initialUser={currentUser?.toJSON()} />

즉, <Header> 구성요소는 가능한 경우 서버 런타임 중에 사용자 데이터를 렌더링합니다. 초기 페이지 로드 후 페이지 수명 주기 동안 인증 업데이트가 있으면 onAuthStateChanged 핸들러가 이를 처리합니다.

이제 웹 앱을 테스트하고 빌드한 내용을 확인합니다.

새 인증 동작을 확인하려면 다음 단계를 따르세요.

  1. 브라우저에서 웹 앱을 새로고침합니다. 표시 이름이 헤더에 표시됩니다.
  2. 로그아웃하고 다시 로그인하세요. 페이지가 새로고침되지 않아도 실시간으로 업데이트됩니다. 다른 사용자에게 이 단계를 반복할 수 있습니다.
  3. 선택사항: 웹 앱을 마우스 오른쪽 버튼으로 클릭하고 페이지 소스 보기를 선택한 후 표시 이름을 검색합니다. 서버에서 반환된 원시 HTML 소스에 나타납니다.

7. 레스토랑 정보 보기

웹 앱에는 레스토랑 및 리뷰에 대한 모의 데이터가 포함되어 있습니다.

레스토랑 하나 이상 추가

로컬 Cloud Firestore 데이터베이스에 모의 레스토랑 데이터를 삽입하려면 다음 단계를 따르세요.

  1. 웹 앱에서 2cf67d488d8e6332.png > 샘플 레스토랑 추가를 선택합니다.
  2. Firebase Console의 Firestore 데이터베이스 페이지에서 레스토랑을 선택합니다. 레스토랑 컬렉션에는 각 레스토랑을 나타내는 최상위 문서가 표시됩니다.
  3. 레스토랑 문서의 속성을 살펴보려면 문서를 몇 개 클릭합니다.

레스토랑 목록 표시

이제 Cloud Firestore 데이터베이스에 Next.js 웹 앱이 표시할 수 있는 레스토랑이 있습니다.

데이터 가져오기 코드를 정의하려면 다음 단계를 따르세요.

  1. src/app/page.js 파일에서 <Home /> 서버 구성요소를 찾고 서버 런타임에 레스토랑 목록을 가져오는 getRestaurants 함수 호출을 검토합니다. 다음 단계에서 getRestaurants 함수를 구현합니다.
  2. src/lib/firebase/firestore.js 파일에서 applyQueryFiltersgetRestaurants 함수를 다음 코드로 바꿉니다.
function applyQueryFilters(q, { category, city, price, sort }) {
        if (category) {
                q = query(q, where("category", "==", category));
        }
        if (city) {
                q = query(q, where("city", "==", city));
        }
        if (price) {
                q = query(q, where("price", "==", price.length));
        }
        if (sort === "Rating" || !sort) {
                q = query(q, orderBy("avgRating", "desc"));
        } else if (sort === "Review") {
                q = query(q, orderBy("numRatings", "desc"));
        }
        return q;
}

export async function getRestaurants(filters = {}) {
        let q = query(collection(db, "restaurants"));

        q = applyQueryFilters(q, filters);
        const results = await getDocs(q);
        return results.docs.map(doc => {
                return {
                        id: doc.id,
                        ...doc.data(),
                        // Only plain objects can be passed to Client Components from Server Components
                        timestamp: doc.data().timestamp.toDate(),
                };
        });
}
  1. 웹 앱을 새로고침합니다. 레스토랑 이미지는 페이지에 타일로 표시됩니다.

서버 런타임에 레스토랑 목록이 로드되는지 확인

Next.js 프레임워크를 사용하면 데이터가 서버 런타임 또는 클라이언트 측 런타임에 로드되는 시점이 명확하지 않을 수 있습니다.

서버 런타임에 레스토랑 목록이 로드되는지 확인하려면 다음 단계를 따르세요.

  1. 웹 앱에서 DevTools를 열고 JavaScript를 사용 중지합니다.

DevTools에서 JavaScript 사용 중지

  1. 웹 앱을 새로고침합니다. 레스토랑 목록이 계속 로드됩니다. 서버 응답에 레스토랑 정보가 반환됩니다. JavaScript가 사용 설정되면 클라이언트 측 JavaScript 코드를 통해 레스토랑 정보가 하드레이션됩니다.
  2. DevTools에서 JavaScript를 다시 사용 설정합니다.

Cloud Firestore 스냅샷 리스너로 레스토랑 업데이트 리슨

이전 섹션에서는 초기 레스토랑 세트가 src/app/page.js 파일에서 로드된 방식을 확인했습니다. src/app/page.js 파일은 서버 구성요소이며 Firebase 데이터 가져오기 코드를 포함하여 서버에서 렌더링됩니다.

src/components/RestaurantListings.jsx 파일은 클라이언트 구성요소이며 서버에서 렌더링된 마크업을 하이드레이션하도록 구성할 수 있습니다.

서버 렌더링 마크업을 하이드레이션하도록 src/components/RestaurantListings.jsx 파일을 구성하려면 다음 단계를 따르세요.

  1. src/components/RestaurantListings.jsx 파일에서 이미 작성된 다음 코드를 확인합니다.
useEffect(() => {
        const unsubscribe = getRestaurantsSnapshot(data => {
                setRestaurants(data);
        }, filters);

        return () => {
                unsubscribe();
        };
}, [filters]);

이 코드는 이전 단계에서 구현한 getRestaurants() 함수와 유사한 getRestaurantsSnapshot() 함수를 호출합니다. 그러나 이 스냅샷 함수는 레스토랑의 컬렉션이 변경될 때마다 콜백이 호출되도록 콜백 메커니즘을 제공합니다.

  1. src/lib/firebase/firestore.js 파일에서 getRestaurantsSnapshot() 함수를 다음 코드로 바꿉니다.
export function getRestaurantsSnapshot(cb, filters = {}) {
        if (typeof cb !== "function") {
                console.log("Error: The callback parameter is not a function");
                return;
        }

        let q = query(collection(db, "restaurants"));
        q = applyQueryFilters(q, filters);

        const unsubscribe = onSnapshot(q, querySnapshot => {
                const results = querySnapshot.docs.map(doc => {
                        return {
                                id: doc.id,
                                ...doc.data(),
                                // Only plain objects can be passed to Client Components from Server Components
                                timestamp: doc.data().timestamp.toDate(),
                        };
                });

                cb(results);
        });

        return unsubscribe;
}

이제 Firestore 데이터베이스 페이지를 통한 변경사항이 웹 앱에 실시간으로 반영됩니다.

  1. 웹 앱에서 27ca5d1e8ed8adfe.png > 샘플 레스토랑 추가를 선택합니다. 스냅샷 함수가 올바르게 구현되면 페이지를 새로고침하지 않아도 레스토랑이 실시간으로 표시됩니다.

8. 웹 앱에서 사용자 데이터 저장

  1. src/lib/firebase/firestore.js 파일에서 updateWithRating() 함수를 다음 코드로 바꿉니다.
const updateWithRating = async (
        transaction,
        docRef,
        newRatingDocument,
        review
) => {
        const restaurant = await transaction.get(docRef);
        const data = restaurant.data();
        const newNumRatings = data?.numRatings ? data.numRatings + 1 : 1;
        const newSumRating = (data?.sumRating || 0) + Number(review.rating);
        const newAverage = newSumRating / newNumRatings;

        transaction.update(docRef, {
                numRatings: newNumRatings,
                sumRating: newSumRating,
                avgRating: newAverage,
        });

        transaction.set(newRatingDocument, {
                ...review,
                timestamp: Timestamp.fromDate(new Date()),
        });
};

이 코드는 새 리뷰를 나타내는 새 Firestore 문서를 삽입합니다. 또한 이 코드는 레스토랑을 나타내는 기존 Firestore 문서도 평점 수와 계산된 평균 평점의 업데이트된 수치로 업데이트합니다.

  1. addReviewToRestaurant() 함수를 코드를 다음 코드로 바꿉니다.
export async function addReviewToRestaurant(db, restaurantId, review) {
        if (!restaurantId) {
                throw new Error("No restaurant ID was provided.");
        }

        if (!review) {
                throw new Error("A valid review has not been provided.");
        }

        try {
                const docRef = doc(collection(db, "restaurants"), restaurantId);
                const newRatingDocument = doc(
                        collection(db, `restaurants/${restaurantId}/ratings`)
                );

                await runTransaction(db, transaction =>
                        updateWithRating(transaction, docRef, newRatingDocument, review)
                );
        } catch (error) {
                console.error(
                        "There was an error adding the rating to the restaurant.",
                        error
                );
                throw error;
        }
}

Next.js 서버 작업 구현

Next.js 서버 작업은 양식 제출 페이로드에서 텍스트 값을 가져오기 위해 data.get("text")와 같은 양식 데이터에 액세스하기 위한 편리한 API를 제공합니다.

Next.js 서버 작업을 사용하여 리뷰 양식 제출을 처리하려면 다음 단계를 따르세요.

  1. src/components/ReviewDialog.jsx 파일의 <form> 요소에서 action 속성을 찾습니다.
<form action={handleReviewFormSubmission}>

action 속성 값은 다음 단계에서 구현하는 함수를 나타냅니다.

  1. src/app/actions.js 파일에서 handleReviewFormSubmission() 함수를 다음 코드로 바꿉니다.
// This is a next.js server action, which is an alpha feature, so
// use with caution.
// https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions
export async function handleReviewFormSubmission(data) {
        const { app } = await getAuthenticatedAppForUser();
        const db = getFirestore(app);

        await addReviewToRestaurant(db, data.get("restaurantId"), {
                text: data.get("text"),
                rating: data.get("rating"),

                // This came from a hidden form field.
                userId: data.get("userId"),
        });
}

레스토랑 리뷰 추가

리뷰 제출에 대한 지원을 구현했으므로 이제 리뷰가 Cloud Firestore에 올바르게 삽입되었는지 확인할 수 있습니다.

리뷰를 추가하고 Cloud Firestore에 삽입되었는지 확인하려면 다음 단계를 따르세요.

  1. 웹 앱에서 홈페이지의 레스토랑을 선택합니다.
  2. 레스토랑 페이지에서 3e19beef78bb0d0e.png를 클릭합니다.
  3. 별표 평점을 선택합니다.
  4. 리뷰를 작성합니다.
  5. 제출을 클릭합니다. 내 리뷰는 리뷰 목록 상단에 표시됩니다.
  6. Cloud Firestore의 문서 추가 창에서 리뷰한 레스토랑의 문서를 검색하여 선택합니다.
  7. 컬렉션 시작 창에서 평점을 선택합니다.
  8. 문서 추가 창에서 검토할 문서를 찾아 예상대로 삽입되었는지 확인합니다.

Firestore 에뮬레이터 문서

9. 웹 앱에서 사용자가 업로드한 파일 저장

이 섹션에서는 로그인했을 때 레스토랑과 관련된 이미지를 바꿀 수 있는 기능을 추가합니다. Firebase Storage에 이미지를 업로드하고 Cloud Firestore 문서에서 레스토랑을 나타내는 이미지 URL을 업데이트합니다.

웹 앱에서 사용자가 업로드한 파일을 저장하려면 다음 단계를 따르세요.

  1. src/components/Restaurant.jsx 파일에서 사용자가 파일을 업로드할 때 실행되는 코드를 확인합니다.
async function handleRestaurantImage(target) {
        const image = target.files ? target.files[0] : null;
        if (!image) {
                return;
        }

        const imageURL = await updateRestaurantImage(id, image);
        setRestaurant({ ...restaurant, photo: imageURL });
}

변경할 필요는 없지만 다음 단계에서 updateRestaurantImage() 함수의 동작을 구현합니다.

  1. src/lib/firebase/storage.js 파일에서 updateRestaurantImage()uploadImage() 함수를 다음 코드로 바꿉니다.
export async function updateRestaurantImage(restaurantId, image) {
        try {
                if (!restaurantId)
                        throw new Error("No restaurant ID has been provided.");

                if (!image || !image.name)
                        throw new Error("A valid image has not been provided.");

                const publicImageUrl = await uploadImage(restaurantId, image);
                await updateRestaurantImageReference(restaurantId, publicImageUrl);

                return publicImageUrl;
        } catch (error) {
                console.error("Error processing request:", error);
        }
}

async function uploadImage(restaurantId, image) {
        const filePath = `images/${restaurantId}/${image.name}`;
        const newImageRef = ref(storage, filePath);
        await uploadBytesResumable(newImageRef, image);

        return await getDownloadURL(newImageRef);
}

updateRestaurantImageReference() 함수는 이미 구현되어 있습니다. 이 함수는 업데이트된 이미지 URL로 Cloud Firestore의 기존 레스토랑 문서를 업데이트합니다.

이미지 업로드 기능 확인

이미지가 예상대로 업로드되는지 확인하려면 다음 단계를 따르세요.

  1. 웹 앱에서 로그인했는지 확인하고 레스토랑을 선택합니다.
  2. 7067eb41fea41ff0.png를 클릭하고 파일 시스템에서 이미지를 업로드합니다. 이미지는 로컬 환경을 벗어나 Cloud Storage에 업로드됩니다. 이미지는 업로드하는 즉시 표시됩니다.
  3. Firebase용 Cloud Storage로 이동합니다.
  4. 레스토랑을 나타내는 폴더로 이동합니다. 업로드한 이미지가 폴더에 있습니다.

6cf3f9e2303c931c.png

10. 결론

수고하셨습니다 Firebase를 사용하여 Next.js 앱에 기능을 추가하는 방법을 알아봤습니다. 구체적으로는 다음을 사용했습니다.

자세히 알아보기