Integracja Firebase z aplikacją Next.js

1. Zanim zaczniesz

Z tego ćwiczenia dowiesz się, jak zintegrować Firebase z aplikacją internetową Next.js o nazwie Friendly Eats, która jest witryną z opiniami o restauracjach.

aplikacja internetowa Friendly Eats,

Gotowa aplikacja internetowa oferuje przydatne funkcje, które pokazują, jak Firebase może pomóc w tworzeniu aplikacji Next.js. Do tych funkcji należą:

  • Automatyczne kompilowanie i wdrażanie: w tym ćwiczeniu używamy Firebase App Hosting do automatycznego kompilowania i wdrażania kodu Next.js za każdym razem, gdy przesyłasz go do skonfigurowanej gałęzi.
  • Logowanie i wylogowywanie: gotowa aplikacja internetowa umożliwia logowanie się przez Google i wylogowywanie się. Logowanie użytkowników i trwałość są zarządzane w całości przez Uwierzytelnianie Firebase.
  • Obrazy: gotowa aplikacja internetowa umożliwia zalogowanym użytkownikom przesyłanie zdjęć restauracji. Komponenty z obrazem są przechowywane w Cloud Storage dla Firebase. Pakiet Firebase JavaScript SDK udostępnia publiczny adres URL przesłanych obrazów. Ten publiczny adres URL jest następnie przechowywany w odpowiednim dokumencie restauracji w Cloud Firestore.
  • Opinie: gotowa aplikacja internetowa umożliwia zalogowanym użytkownikom publikowanie opinii o restauracjach, które składają się z oceny w gwiazdkach i wiadomości tekstowej. Informacje o opiniach są przechowywane w Cloud Firestore.
  • Filtry: gotowa aplikacja internetowa umożliwia zalogowanym użytkownikom filtrowanie listy restauracji według kategorii, lokalizacji i ceny. Możesz też dostosować metodę sortowania. Dane są pobierane z Cloud Firestore, a zapytania Firestore są stosowane na podstawie użytych filtrów.

Wymagania wstępne

  • konto GitHub,
  • Znajomość Next.js i JavaScriptu

Czego się nauczysz

Czego potrzebujesz

  • Git
  • najnowszą stabilną wersję Node.js,
  • wybraną przeglądarkę, np. Google Chrome;
  • środowisko programistyczne z edytorem kodu i terminalem,
  • konto Google do tworzenia projektu w Firebase i zarządzania nim;
  • Możliwość przejścia na abonament Blaze w projekcie w Firebase.

2. Konfigurowanie środowiska programistycznego i repozytorium GitHub

To ćwiczenie zawiera początkową bazę kodu aplikacji i korzysta z interfejsu wiersza poleceń Firebase.

Tworzenie repozytorium GitHub

Kod źródłowy ćwiczeń z programowania znajdziesz na stronie https://github.com/firebase/friendlyeats-web. Repozytorium zawiera przykładowe projekty dla wielu platform. W tym ćwiczeniu używamy jednak tylko katalogu nextjs-start. Zwróć uwagę na te katalogi:

* `nextjs-start`: contains the starter code upon which you build.
* `nextjs-end`: contains the solution code for the finished web app.

Skopiuj folder nextjs-start do własnego repozytorium:

  1. W terminalu utwórz nowy folder na komputerze i przejdź do nowego katalogu:
    mkdir codelab-friendlyeats-web
    
    cd codelab-friendlyeats-web
    
  2. Użyj pakietu npm giget, aby pobrać tylko folder nextjs-start:
    npx giget@latest "gh:firebase/friendlyeats-web/nextjs-start#master" . --install
    
  3. Śledź zmiany lokalnie za pomocą Gita:
    git init
    
    git add .
    
    git commit -m "Codelab starting point"
    
    git branch -M main
    
  4. Utwórz nowe repozytorium GitHub: https://github.com/new. Możesz nadać mu dowolną nazwę.
  5. W zależności od tego, jak uwierzytelniasz się w GitHubie (HTTPS lub SSH), skopiuj nowy adres URL utworzony przez GitHuba:
    • https://github.com/<USER_NAME>/<REPOSITORY_NAME>.git lub
    • git@github.com:<USER_NAME>/<REPOSITORY_NAME>.git
  6. Prześlij zmiany lokalne do nowego repozytorium GitHub, uruchamiając to polecenie. Zastąp obiekt zastępczy <REPOSITORY_URL> rzeczywistym adresem URL repozytorium.
    git remote add origin <REPOSITORY_URL>
    
    git push -u origin main
    
  7. W repozytorium GitHub powinien pojawić się kod startowy.

Instalowanie i aktualizowanie interfejsu wiersza poleceń Firebase

Uruchom to polecenie, aby sprawdzić, czy masz zainstalowany interfejs wiersza poleceń Firebase w wersji 14.1.0 lub nowszej:

firebase --version

Jeśli widzisz starszą wersję lub nie masz zainstalowanego wiersza poleceń Firebase, uruchom polecenie instalacji:

npm install -g firebase-tools@latest

Jeśli nie możesz zainstalować interfejsu wiersza poleceń Firebase z powodu błędów uprawnień, zapoznaj się z dokumentacją npm lub użyj innej opcji instalacji.

Zaloguj się w Firebase.

  1. Uruchom to polecenie, aby zalogować się w interfejsie wiersza poleceń Firebase:
    firebase login
    
  2. W zależności od tego, czy chcesz, aby Firebase zbierał dane, wpisz Y lub N.
  3. W przeglądarce wybierz swoje konto Google, a następnie kliknij Zezwól.

3. Konfigurowanie projektu w Firebase

W tej sekcji skonfigurujesz projekt w Firebase i powiążesz z nim aplikację internetową Firebase. Skonfigurujesz też usługi Firebase używane przez przykładową aplikację internetową.

Tworzenie projektu w Firebase

  1. Zaloguj się w konsoli Firebase, używając tego samego konta Google co w poprzednim kroku.
  2. Kliknij przycisk, aby utworzyć nowy projekt, a następnie wpisz jego nazwę (np. FriendlyEats Codelab).
  3. Kliknij Dalej.
  4. Jeśli pojawi się taka prośba, przeczytaj i zaakceptuj Warunki korzystania z Firebase, a następnie kliknij Dalej.
  5. (Opcjonalnie) Włącz pomoc AI w konsoli Firebase (funkcja „Gemini w Firebase”).
  6. W tym ćwiczeniu nie potrzebujesz Google Analytics, więc wyłącz tę opcję.
  7. Kliknij Utwórz projekt, poczekaj na udostępnienie projektu, a następnie kliknij Dalej.

Przejście na wyższy abonament Firebase

Aby korzystać z Firebase App Hosting i Cloud Storage dla Firebase, projekt Firebase musi być objęty abonamentem Blaze (płatność według wykorzystania), co oznacza, że jest on połączony z kontem rozliczeniowym Cloud.

Aby przejść na abonament Blaze, wykonaj te czynności:

  1. W konsoli Firebase wybierz opcję przejścia na wyższy abonament.
  2. Wybierz abonament Blaze. Postępuj zgodnie z instrukcjami wyświetlanymi na ekranie, aby połączyć konto rozliczeniowe Cloud z projektem.
    Jeśli w ramach tego uaktualnienia konieczne było utworzenie konta rozliczeniowego Cloud, może być konieczne powrócenie do procesu uaktualniania w konsoli Firebase, aby dokończyć uaktualnianie.

Dodawanie aplikacji internetowej do projektu w Firebase

  1. W projekcie w Firebase otwórz Przegląd projektu, kliknij Dodaj aplikację, a następnie Sieć.
  2. W polu tekstowym Nazwa aplikacji wpisz łatwą do zapamiętania nazwę aplikacji, np. My Next.js app.
  3. Nie zaznaczaj pola wyboru Skonfiguruj też Hosting Firebase dla tej aplikacji.
  4. Kliknij Zarejestruj aplikację > Przejdź do konsoli.

Konfigurowanie usług Firebase w konsoli Firebase

Konfigurowanie uwierzytelniania

  1. W panelu po lewej stronie konsoli Firebase rozwiń Build (Kompilacja), a następnie wybierz Authentication (Uwierzytelnianie).
  2. Kliknij Rozpocznij.
  3. W kolumnie Dostawcy logowania kliknij Google > Włącz.
  4. W polu tekstowym Publiczna nazwa projektu wpisz łatwą do zapamiętania nazwę, np. My Next.js app.
  5. Z menu Adres e-mail pomocy dla projektu wybierz swój adres e-mail.
  6. Kliknij Zapisz.

Konfigurowanie Cloud Firestore

  1. W lewym panelu konsoli Firebase rozwiń Kompilacja, a następnie wybierz Baza danych Firestore.
  2. Kliknij Utwórz bazę danych.
  3. Wybierz Wersja standardowa i kliknij Dalej.
  4. Nie zmieniaj identyfikatora bazy danych, pozostaw wartość (default).
  5. Wybierz lokalizację bazy danych, a następnie kliknij Dalej.
    W przypadku prawdziwej aplikacji wybierz lokalizację, która jest blisko użytkowników.
  6. Kliknij Uruchom w trybie testowym. Przeczytaj wyłączenie odpowiedzialności dotyczące reguł zabezpieczeń.
    W dalszej części tego ćwiczenia dodasz reguły zabezpieczeń, aby chronić swoje dane. Nie rozpowszechniaj ani nie udostępniaj publicznie aplikacji bez dodania reguł zabezpieczeń do bazy danych.
  7. Kliknij Utwórz.

Konfigurowanie Cloud Storage dla Firebase

  1. W lewym panelu konsoli Firebase rozwiń Kompilacja, a następnie wybierz Miejsce na dane.
  2. Kliknij Rozpocznij.
  3. Wybierz lokalizację domyślnego zasobnika Storage.
    Zasobniki w regionach US-WEST1, US-CENTRAL1 i US-EAST1 mogą korzystać z poziomu „Zawsze bezpłatnie” w Google Cloud Storage. Zasobniki we wszystkich innych lokalizacjach podlegają cenom i zasadom korzystania z Google Cloud Storage.
  4. Kliknij Uruchom w trybie testowym. Przeczytaj wyłączenie odpowiedzialności dotyczące reguł zabezpieczeń.
    W dalszej części tego ćwiczenia dodasz reguły zabezpieczeń, aby chronić swoje dane. Nie rozpowszechniaj ani nie udostępniaj publicznie aplikacji bez dodania reguł zabezpieczeń do zasobnika Storage.
  5. Kliknij Utwórz.

Wdrażanie reguł zabezpieczeń

Kod zawiera już zestawy reguł zabezpieczeń dla Firestore i Cloud Storage dla Firebase. Po wdrożeniu reguł zabezpieczeń dane w bazie danych i zasobniku są lepiej chronione przed nadużyciami.

  1. W terminalu skonfiguruj interfejs wiersza poleceń, aby używał utworzonego wcześniej projektu w Firebase:
    firebase use --add
    
    Gdy pojawi się prośba o alias, wpisz friendlyeats-codelab.
  2. Aby wdrożyć te reguły zabezpieczeń (oraz indeksy, które będą potrzebne później), uruchom w terminalu to polecenie:
    firebase deploy --only firestore,storage
    
  3. Jeśli pojawi się pytanie: "Cloud Storage for Firebase needs an IAM Role to use cross-service rules. Grant the new role?", naciśnij Enter, aby wybrać Tak.

4. Przeglądanie początkowej bazy kodu

W tej sekcji omówimy kilka obszarów początkowej bazy kodu aplikacji, do których dodasz funkcje w tym ćwiczeniu.

Struktura folderów i plików

Tabela poniżej zawiera przegląd struktury folderów i plików aplikacji:

Foldery i pliki

Opis

src/components

komponenty React do filtrów, nagłówków, szczegółów restauracji i opinii;

src/lib

funkcje użytkowe, które niekoniecznie są powiązane z Reactem lub Next.js;

src/lib/firebase

Kod Firebase i konfiguracja Firebase

public

zasoby statyczne w aplikacji internetowej, np. ikony;

src/app

Routing za pomocą routera aplikacji Next.js

package.json i package-lock.json

Zależności projektu z npm

next.config.js

Konfiguracja Next.js (działania serwera są włączone)

jsconfig.json

Konfiguracja usługi językowej JavaScript

Komponenty serwera i klienta

Aplikacja internetowa Next.js korzysta z App Router. W całej aplikacji używane jest renderowanie po stronie serwera. Na przykład plik src/app/page.js to komponent serwera odpowiedzialny za stronę główną. Plik src/components/RestaurantListings.jsx to komponent klienta oznaczony dyrektywą "use client" na początku pliku.

Instrukcje importu

Możesz zauważyć instrukcje importu podobne do tych:

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

Aplikacja używa symbolu @, aby uniknąć nieporęcznych względnych ścieżek importu, co jest możliwe dzięki aliasom ścieżek.

Interfejsy API Firebase

Cały kod interfejsu Firebase API jest opakowany w katalogu src/lib/firebase. Poszczególne komponenty React importują następnie opakowane funkcje z katalogu src/lib/firebase, zamiast importować funkcje Firebase bezpośrednio.

Dane próbne

Przykładowe dane dotyczące restauracji i opinii znajdują się w pliku src/lib/randomData.js. Dane z tego pliku są składane w kodzie w pliku src/lib/fakeRestaurants.js.

5. Tworzenie backendu App Hosting

W tej sekcji skonfigurujesz backend App Hosting, aby obserwować gałąź w repozytorium Git.

Po ukończeniu tej sekcji będziesz mieć backend App Hosting połączony z repozytorium na GitHubie, który będzie automatycznie ponownie kompilować i wdrażać nową wersję aplikacji za każdym razem, gdy prześlesz nowe zatwierdzenie do gałęzi main.

Utworzenie backendu

  1. W konsoli Firebase otwórz stronę App Hosting.
  2. Kliknij Rozpocznij, aby rozpocząć proces tworzenia backendu.
  3. Wybierz region. W przypadku prawdziwej aplikacji wybierz region najbliżej użytkowników.
  4. Aby skonfigurować uwierzytelnianie GitHub, postępuj zgodnie z instrukcjami w kroku Importowanie repozytorium GitHub.
  5. W sekcji Repozytorium wybierz Przyznaj dostęp do nowego repozytorium w GitHubie i postępuj zgodnie z instrukcjami, aby włączyć dostęp do utworzonego wcześniej repozytorium GitHub.
  6. Kliknij Odśwież listę, aby odświeżyć listę, a następnie wybierz repozytorium i kliknij Dalej.
  7. Skonfiguruj ustawienia wdrażania:
    1. Ustaw aktywną gałąź na main.
    2. Pozostaw katalog główny jako /.
    3. Włącz automatyczne wdrażanie.
  8. Nazwij backend friendlyeats-codelab i kliknij Dalej.
  9. W sekcji Powiąż aplikację internetową Firebase wybierz Wybierz istniejącą aplikację internetową Firebase i wybierz dodaną aplikację z listy.
  10. Kliknij Zakończ i wdróż. Nastąpi przekierowanie na nową stronę, na której możesz zobaczyć stan nowego backendu App Hosting.
  11. Kliknij Wyświetl, aby zobaczyć więcej informacji o wdrożeniu App Hosting, w tym stan wdrażania, logi i szczegóły dotyczące wykorzystania.
  12. Po zakończeniu wdrażania kliknij, aby otworzyć adres URL witryny w sekcji Domeny. Zanim zacznie działać, może minąć kilka minut ze względu na propagację DNS.
  13. Ojej. Gdy strona się wczyta, zobaczysz komunikat o błędzie „Błąd aplikacji: wystąpił wyjątek po stronie serwera (więcej informacji znajdziesz w logach serwera)”.
  14. W konsoli Firebase sprawdź kartę Logi backendu App Hosting. Zobaczysz dziennik „Błąd: nie zaimplementowano”. Naprawimy to w następnym kroku, gdy dodamy uwierzytelnianie.

Udało Ci się wdrożyć początkową aplikację internetową. Za każdym razem, gdy przeniesiesz nowe zatwierdzenie do gałęzi main repozytorium GitHub, w konsoli Firebase rozpocznie się nowa kompilacja i wdrożenie, a po zakończeniu wdrażania witryna zostanie automatycznie zaktualizowana.

6. Dodawanie uwierzytelniania do aplikacji internetowej

W tej sekcji dodasz uwierzytelnianie do aplikacji internetowej, aby móc się do niej zalogować.

Dodaj autoryzowaną domenę

Uwierzytelnianie Firebase będzie akceptować tylko żądania logowania z domen, na które zezwolisz. W tym miejscu dodamy domenę backendu App Hosting do listy zatwierdzonych domen w projekcie.

  1. Otwórz stronę App Hosting i kliknij Wyświetl pod wdrożoną witryną, aby otworzyć stronę Przegląd. Skopiuj nazwę domeny backendu App Hosting.
  2. Otwórz kartę Ustawienia uwierzytelniania i wybierz projekt, do którego chcesz dodać autoryzowaną domenę. Następnie znajdź sekcję Autoryzowane domeny i kliknij ją.
  3. Kliknij przycisk Dodaj domenę.
  4. Wpisz domenę backendu App Hosting.
  5. Kliknij Dodaj.

Wdrażanie funkcji logowania i wylogowywania

W pliku src/lib/firebase/auth.js zastąp funkcje onAuthStateChanged, onIdTokenChanged, signInWithGoogle i signOut tym kodem:

export function onAuthStateChanged(cb) {
  return _onAuthStateChanged(auth, cb);
}

export function onIdTokenChanged(cb) {
  return _onIdTokenChanged(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);
  }
}

Ten kod używa tych interfejsów Firebase API:

Interfejs API Firebase

Opis

auth.onAuthStateChanged

Dodaje obserwatora zmian stanu logowania użytkownika.

auth.onIdTokenChanged

Dodaje obserwatora zmian w tokenie identyfikacji użytkownika.

GoogleAuthProvider

Tworzy instancję dostawcy uwierzytelniania Google.

signInWithPopup

Rozpoczyna proces uwierzytelniania oparty na oknie.

auth.signOut

Wylogowuje użytkownika.

W pliku src/components/Header.jsx kod wywołuje już funkcje signInWithGoogle i signOut.

Wysyłanie stanu uwierzytelniania do serwera

Aby przekazać stan uwierzytelniania do serwera, użyjemy plików cookie. Za każdym razem, gdy stan uwierzytelniania zmieni się w kliencie, zaktualizujemy plik cookie __session.

W pliku src/components/Header.jsx zastąp funkcję useUserSession tym kodem:

function useUserSession(initialUser) {
  useEffect(() => {
    return onIdTokenChanged(async (user) => {
      if (user) {
        const idToken = await user.getIdToken();
        await setCookie("__session", idToken);
      } else {
        await deleteCookie("__session");
      }
      if (initialUser?.uid === user?.uid) {
        return;
      }
      window.location.reload();
    });
  }, [initialUser]);

  return initialUser;
}

Odczytywanie stanu uwierzytelniania na serwerze

Użyjemy FirebaseServerApp, aby odzwierciedlić stan uwierzytelniania klienta na serwerze.

Otwórz src/lib/firebase/serverApp.js i zastąp funkcję getAuthenticatedAppForUser:

export async function getAuthenticatedAppForUser() {
  const authIdToken = (await cookies()).get("__session")?.value;

  // Firebase Server App is a new feature in the JS SDK that allows you to
  // instantiate the SDK with credentials retrieved from the client & has
  // other affordances for use in server environments.
  const firebaseServerApp = initializeServerApp(
    // https://github.com/firebase/firebase-js-sdk/issues/8863#issuecomment-2751401913
    initializeApp(),
    {
      authIdToken,
    }
  );

  const auth = getAuth(firebaseServerApp);
  await auth.authStateReady();

  return { firebaseServerApp, currentUser: auth.currentUser };
}

Zweryfikuj zmiany

Układ główny w pliku src/app/layout.js renderuje nagłówek i przekazuje użytkownika (jeśli jest dostępny) jako właściwość.

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

Oznacza to, że komponent <Header> renderuje dane użytkownika, jeśli są dostępne, podczas działania serwera. Jeśli po początkowym wczytaniu strony w trakcie jej cyklu życia nastąpią jakieś aktualizacje uwierzytelniania, zostaną one obsłużone przez moduł obsługi onAuthStateChanged.

Teraz nadszedł czas na wdrożenie nowej kompilacji i sprawdzenie, co udało się stworzyć.

  1. Utwórz zatwierdzenie z komunikatem „Add authentication” i prześlij je do repozytorium GitHub.
    git add .
    
    git commit -m "Add authentication"
    
    git push
    
  2. Otwórz stronę App Hosting i po zakończeniu nowego wdrożenia kliknij adres URL witryny, aby ją otworzyć.
  3. Testowanie uwierzytelniania:
    1. Zaloguj się na konto Google i sprawdź, czy po zalogowaniu się w nagłówku wyświetla się Twoja wyświetlana nazwa.
    2. Wyloguj się i zaloguj jeszcze raz. Możesz powtórzyć ten krok z innymi użytkownikami.
    3. Opcjonalnie: kliknij prawym przyciskiem myszy aplikację internetową, wybierz Wyświetl źródło strony i wyszukaj wyświetlaną nazwę. Znajduje się w nieprzetworzonym źródle HTML zwróconym z serwera.

7. Wyświetlanie informacji o restauracji

Aplikacja internetowa zawiera przykładowe dane dotyczące restauracji i opinii.

Dodaj co najmniej 1 restaurację

Aby wstawić przykładowe dane restauracji do lokalnej bazy danych Cloud Firestore, wykonaj te czynności:

  1. Zaloguj się w aplikacji internetowej, jeśli jeszcze nie masz konta. Następnie kliknij 2cf67d488d8e6332.png > Dodaj przykładowe restauracje. Zauważ, że w aplikacji internetowej Friendly Eats nie wyświetlają się żadne restauracje, ponieważ nie skonfigurowaliśmy jeszcze kodu pobierania danych. Naprawimy to w następnym kroku.
  2. W konsoli Firebase na stronie Baza danych Firestore wybierz restauracje. Zobaczysz dokumenty najwyższego poziomu w kolekcji restauracji, z których każdy reprezentuje restaurację.
  3. Kliknij kilka dokumentów, aby zapoznać się z właściwościami dokumentu restauracji.

Wyświetl listę restauracji

Baza danych Cloud Firestore zawiera teraz restauracje, które aplikacja internetowa Next.js może wyświetlać.

Aby zdefiniować kod pobierania danych, wykonaj te czynności:

  1. W pliku src/app/page.js znajdź komponent serwera <Home /> i sprawdź wywołanie funkcji getRestaurants, która pobiera listę restauracji w czasie działania serwera. W kolejnych krokach zaimplementujesz funkcję getRestaurants.
  2. W pliku src/lib/firebase/firestore.js zastąp funkcje applyQueryFilters i getRestaurants tym kodem:
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(db = db, 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. Utwórz zatwierdzenie z komunikatem „Read the list of restaurants from Firestore” (Odczytaj listę restauracji z Firestore) i prześlij je do repozytorium GitHub.
    git add .
    
    git commit -m "Read the list of restaurants from Firestore"
    
    git push
    
  2. Otwórz stronę App Hosting w konsoli Firebase i poczekaj na zakończenie nowego wdrożenia.
  3. Odśwież stronę w aplikacji internetowej. Zdjęcia restauracji wyświetlają się na stronie jako kafelki.

Sprawdzanie, czy informacje o restauracjach wczytują się w czasie działania serwera

W przypadku platformy Next.js może nie być oczywiste, kiedy dane są wczytywane w czasie działania serwera, a kiedy w czasie działania po stronie klienta.

Aby sprawdzić, czy informacje o restauracjach są wczytywane w czasie działania serwera, wykonaj te czynności:

  1. W aplikacji internetowej otwórz Narzędzia deweloperskie i wyłącz JavaScript.

Wyłączanie JavaScriptu w Narzędziach deweloperskich

  1. Odśwież aplikację internetową. Lista restauracji nadal się wczytuje. Informacje o restauracji są zwracane w odpowiedzi serwera. Gdy JavaScript jest włączony, informacje o restauracji są pobierane za pomocą kodu JavaScript po stronie klienta.
  2. W Narzędziach deweloperskich ponownie włącz JavaScript.

Nasłuchiwanie aktualizacji restauracji za pomocą detektorów zrzutów Cloud Firestore

W poprzedniej sekcji pokazaliśmy, jak początkowy zestaw restauracji został wczytany z pliku src/app/page.js. Plik src/app/page.js jest komponentem serwera i jest renderowany na serwerze, w tym kodzie Firebase do pobierania danych.

Plik src/components/RestaurantListings.jsx jest komponentem klienta i można go skonfigurować tak, aby nawadniał znaczniki renderowane przez serwer.

Aby skonfigurować plik src/components/RestaurantListings.jsx do hydratacji znaczników renderowanych po stronie serwera, wykonaj te czynności:

  1. W pliku src/components/RestaurantListings.jsx znajdziesz ten kod, który został już napisany:
useEffect(() => {
    return getRestaurantsSnapshot((data) => {
      setRestaurants(data);
    }, filters);
  }, [filters]);

Ten kod wywołuje funkcję getRestaurantsSnapshot(), która jest podobna do funkcji getRestaurants() zaimplementowanej w poprzednim kroku. Ta funkcja zrzutu udostępnia jednak mechanizm wywołania zwrotnego, dzięki czemu wywołanie zwrotne jest wywoływane za każdym razem, gdy w kolekcji restauracji zostanie wprowadzona zmiana.

  1. W pliku src/lib/firebase/firestore.js zastąp funkcję getRestaurantsSnapshot() tym kodem:
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);

  return 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);
  });
}
  1. W pliku src/lib/firebase/firestore.js zastąp funkcję getRestaurantSnapshotById() tym kodem:
export function getRestaurantSnapshotById(restaurantId, cb) {
  if (!restaurantId) {
    console.log("Error: Invalid ID received: ", restaurantId);
    return;
  }

  if (typeof cb !== "function") {
    console.log("Error: The callback parameter is not a function");
    return;
  }

  const docRef = doc(db, "restaurants", restaurantId);
  return onSnapshot(docRef, (docSnap) => {
    cb({
      ...docSnap.data(),
      timestamp: docSnap.data().timestamp.toDate(),
    });
  });
}

Zmiany wprowadzone na stronie Baza danych Firestore są teraz odzwierciedlane w aplikacji internetowej w czasie rzeczywistym.

  1. Utwórz zatwierdzenie z komunikatem „Listen for realtime restaurant updates” i prześlij je do repozytorium GitHub.
    git add .
    
    git commit -m "Listen for realtime restaurant updates"
    
    git push
    
  2. Otwórz stronę App Hosting w konsoli Firebase i poczekaj na zakończenie nowego wdrożenia.
  3. W aplikacji internetowej kliknij 27ca5d1e8ed8adfe.png > Dodaj przykładowe restauracje. Jeśli funkcja migawki jest prawidłowo zaimplementowana, restauracje pojawiają się w czasie rzeczywistym bez odświeżania strony.

8. Zapisywanie opinii przesłanych przez użytkowników z aplikacji internetowej

  1. W pliku src/lib/firebase/firestore.js zastąp funkcję updateWithRating() tym kodem:
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()),
  });
};

Ten kod wstawia nowy dokument Firestore reprezentujący nową opinię. Kod aktualizuje też istniejący dokument Firestore, który reprezentuje restaurację, o zaktualizowane dane dotyczące liczby ocen i obliczonej średniej oceny.

  1. Zastąp funkcję addReviewToRestaurant() tym kodem:
export async function addReviewToRestaurant(db, restaurantId, review) {
  if (!restaurantId) {
    throw new Error("No restaurant ID has been 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`),
    );

    // corrected line
    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;
  }
}

Implementowanie działania serwera Next.js

Działanie serwera Next.js udostępnia wygodny interfejs API do uzyskiwania dostępu do danych formularza, takich jak data.get("text"), aby pobrać wartość tekstową z ładunku przesłanego formularza.

Aby użyć akcji serwera Next.js do przetworzenia przesłanego formularza opinii, wykonaj te czynności:

  1. W pliku src/components/ReviewDialog.jsx znajdź atrybut action w elemencie <form>.
<form
  action={handleReviewFormSubmission}
  onSubmit={() => {
    handleClose();
  }}
>

Wartość atrybutu action odnosi się do funkcji, którą zaimplementujesz w następnym kroku.

  1. W pliku src/app/actions.js zastąp funkcję handleReviewFormSubmission() tym kodem:
export async function handleReviewFormSubmission(data) {
  const { firebaseServerApp } = await getAuthenticatedAppForUser();
  const db = getFirestore(firebaseServerApp);

  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"),
  });
}

Dodawanie opinii o restauracji

Obsługa przesyłania opinii została wdrożona, więc teraz możesz sprawdzić, czy opinie są prawidłowo wstawiane do Cloud Firestore.

Aby dodać opinię i sprawdzić, czy została wstawiona do Cloud Firestore, wykonaj te czynności:

  1. Utwórz zatwierdzenie z komunikatem „Allow users to submit restaurant reviews” i prześlij je do repozytorium GitHub.
    git add .
    
    git commit -m "Allow users to submit restaurant reviews"
    
    git push
    
  2. Otwórz stronę App Hosting w konsoli Firebase i poczekaj na zakończenie nowego wdrożenia.
  3. Odśwież aplikację internetową i wybierz restaurację na stronie głównej.
  4. Na stronie restauracji kliknij 3e19beef78bb0d0e.png.
  5. Wybierz liczbę gwiazdek.
  6. Napisz opinię.
  7. Kliknij Prześlij. Twoja opinia pojawi się u góry listy opinii.
  8. W Cloud Firestore wyszukaj w panelu Dodaj dokument dokument restauracji, którą oceniasz, i wybierz go.
  9. W panelu Rozpocznij zbieranie danych wybierz oceny.
  10. W panelu Dodaj dokument znajdź dokument do sprawdzenia, aby upewnić się, że został wstawiony zgodnie z oczekiwaniami.

9. Zapisywanie plików przesłanych przez użytkowników z aplikacji internetowej

W tej sekcji dodasz funkcję, która pozwoli Ci zastąpić obraz powiązany z restauracją, gdy się zalogujesz. Prześlesz obraz do Pamięci Firebase i zaktualizujesz adres URL obrazu w dokumencie Cloud Firestore, który reprezentuje restaurację.

Aby zapisać pliki przesłane przez użytkowników z aplikacji internetowej, wykonaj te czynności:

  1. W pliku src/components/Restaurant.jsx sprawdź kod, który jest uruchamiany, gdy użytkownik przesyła plik:
async function handleRestaurantImage(target) {
  const image = target.files ? target.files[0] : null;
  if (!image) {
    return;
  }

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

Nie musisz wprowadzać żadnych zmian w tej funkcji, ale w kolejnych krokach zaimplementujesz działanie funkcji updateRestaurantImage().

  1. W pliku src/lib/firebase/storage.js zastąp funkcje updateRestaurantImage() i uploadImage() tym kodem:
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);
}

Funkcja updateRestaurantImageReference() jest już dla Ciebie wdrożona. Ta funkcja aktualizuje istniejący dokument restauracji w Cloud Firestore, dodając zaktualizowany adres URL obrazu.

Sprawdzanie funkcji przesyłania obrazów

Aby sprawdzić, czy obraz został przesłany prawidłowo:

  1. Utwórz zatwierdzenie z komunikatem „Zezwalaj użytkownikom na zmianę zdjęcia każdej restauracji” i prześlij je do repozytorium GitHub.
    git add .
    
    git commit -m "Allow users to change each restaurants' photo"
    
    git push
    
  2. Otwórz stronę App Hosting w konsoli Firebase i poczekaj na zakończenie nowego wdrożenia.
  3. W aplikacji internetowej sprawdź, czy jesteś zalogowany(-a), i wybierz restaurację.
  4. Kliknij 7067eb41fea41ff0.png i prześlij obraz z systemu plików. Obraz opuszcza środowisko lokalne i jest przesyłany do Cloud Storage. Obraz pojawi się natychmiast po przesłaniu.
  5. Otwórz Cloud Storage dla Firebase.
  6. Przejdź do folderu, który reprezentuje restaurację. Przesłany obraz znajduje się w folderze.

6cf3f9e2303c931c.png

10. Podsumowywanie opinii o restauracjach za pomocą generatywnej AI

W tej sekcji dodasz funkcję podsumowania opinii, dzięki której użytkownik będzie mógł szybko dowiedzieć się, co inni myślą o restauracji, bez konieczności czytania każdej opinii.

Przechowywanie klucza interfejsu Gemini API w usłudze Cloud Secret Manager

  1. Aby korzystać z interfejsu Gemini API, potrzebujesz klucza interfejsu API. Otwórz Google AI Studio i kliknij Utwórz klucz interfejsu API.
  2. Nazwij klucz, jak chcesz. Jeśli Twojego projektu nie ma na liście Wybierz zaimportowany projekt, kliknij Importuj projekt, zaznacz projekt na liście i kliknij Importuj. Na koniec wybierz go w sekcji Wybierz zaimportowany projekt i kliknij Utwórz klucz.
  3. App Hosting integruje się z Cloud Secret Manager, aby umożliwić bezpieczne przechowywanie wartości poufnych, takich jak klucze interfejsu API:
    1. W terminalu uruchom polecenie, aby utworzyć nowy obiekt tajny:
    firebase apphosting:secrets:set GEMINI_API_KEY
    
    1. Gdy pojawi się prośba o podanie wartości obiektu tajnego, skopiuj i wklej klucz interfejsu Gemini API z Google AI Studio.
    2. Gdy pojawi się pytanie, czy nowy klucz tajny jest przeznaczony do środowiska produkcyjnego czy do testów lokalnych, wybierz „Produkcja”.
    3. Gdy pojawi się pytanie, czy chcesz przyznać dostęp, aby konto usługi backendu mogło uzyskać dostęp do obiektu tajnego, wybierz „Tak”.
    4. Gdy pojawi się pytanie, czy nowy obiekt tajny ma zostać dodany do apphosting.yaml, wpisz Y, aby zaakceptować.

Klucz interfejsu Gemini API jest teraz bezpiecznie przechowywany w usłudze Cloud Secret Manager i jest dostępny dla backendu App Hosting.

Implementowanie komponentu podsumowania opinii

  1. W pliku src/components/Reviews/ReviewSummary.jsx zastąp funkcję GeminiSummary tym kodem:
    export async function GeminiSummary({ restaurantId }) {
      const { firebaseServerApp } = await getAuthenticatedAppForUser();
      const reviews = await getReviewsByRestaurantId(
        getFirestore(firebaseServerApp),
        restaurantId
      );
    
      const reviewSeparator = "@";
      const prompt = `
        Based on the following restaurant reviews, 
        where each review is separated by a '${reviewSeparator}' character, 
        create a one-sentence summary of what people think of the restaurant. 
    
        Here are the reviews: ${reviews.map((review) => review.text).join(reviewSeparator)}
      `;
    
      try {
        if (!process.env.GEMINI_API_KEY) {
          // Make sure GEMINI_API_KEY environment variable is set:
          // https://genkit.dev/docs/get-started/
          throw new Error(
            'GEMINI_API_KEY not set. Set it with "firebase apphosting:secrets:set GEMINI_API_KEY"'
          );
        }
    
        // Configure a Genkit instance.
        const ai = genkit({
          plugins: [googleAI()],
          model: gemini20Flash, // set default model
        });
        const { text } = await ai.generate(prompt);
    
        return (
          <div className="restaurant__review_summary">
            <p>{text}</p>
            <p>✨ Summarized with Gemini</p>
          </div>
        );
      } catch (e) {
        console.error(e);
        return <p>Error summarizing reviews.</p>;
      }
    }
    
  2. Utwórz zatwierdzenie z komunikatem „Use AI to summarize reviews” (Użyj AI do podsumowywania opinii) i prześlij je do repozytorium GitHub.
    git add .
    
    git commit -m "Use AI to summarize reviews"
    
    git push
    
  3. Otwórz stronę App Hosting w konsoli Firebase i poczekaj na zakończenie nowego wdrożenia.
  4. Otwórz stronę restauracji. U góry powinno być widoczne jednozdaniowe podsumowanie wszystkich opinii na stronie.
  5. Dodaj nową opinię i odśwież stronę. Podsumowanie powinno się zmienić.

11. Cofanie publikacji witryny App Hosting

Jeśli po ukończeniu tego ćwiczenia nie zamierzasz dalej korzystać z aplikacji, możesz ją wycofać, aby nikt nie miał dostępu do Twoich zasobów Firestore, Storage i Gemini. W każdej chwili możesz ponownie opublikować stronę.

Aby cofnąć publikację witryny App Hosting:

  1. Otwórz App Hosting w konsoli Firebase.
  2. Znajdź backend aplikacji i kliknij Wyświetl.
  3. W sekcji Informacje o backendzie obok opcji Domeny kliknij Zarządzaj. Spowoduje to wczytanie strony Domeny.
  4. Obok domeny kliknij ikonę Więcej (3 pionowe kropki), wybierz Wyłącz domenę, a następnie kliknij Wyłącz, aby potwierdzić.

12. Podsumowanie

Gratulacje! Wiesz już, jak używać Firebase do dodawania funkcji do aplikacji Next.js. W szczególności udało Ci się:

Więcej informacji