Skip to content

Commit

Permalink
update to Next 14 and App Hosting (#287)
Browse files Browse the repository at this point in the history
  • Loading branch information
jhuleatt authored May 14, 2024
1 parent 5ac7683 commit ddc901f
Show file tree
Hide file tree
Showing 56 changed files with 882 additions and 624 deletions.
3 changes: 2 additions & 1 deletion nextjs-end/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
lib/firebase/config.js
.next/
.firebase/
.firebase/
node_modules/
17 changes: 17 additions & 0 deletions nextjs-end/apphosting.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
env:
# Set this with firebase apphosting:secrets:set
- variable: GEMINI_API_KEY
secret: gemini-api-key
# Get these values from the Firebase console
- variable: NEXT_PUBLIC_FIREBASE_API_KEY
value: TODO
- variable: NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN
value: TODO
- variable: NEXT_PUBLIC_FIREBASE_PROJECT_ID
value: TODO
- variable: NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET
value: TODO
- variable: NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID
value: TODO
- variable: NEXT_PUBLIC_FIREBASE_APP_ID
value: TODO
45 changes: 45 additions & 0 deletions nextjs-end/auth-service-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { initializeApp } from "firebase/app";
import { getAuth, getIdToken } from "firebase/auth";
import { getInstallations, getToken } from "firebase/installations";

// this is set during install
let firebaseConfig;

self.addEventListener('install', event => {
// extract firebase config from query string
const serializedFirebaseConfig = new URL(location).searchParams.get('firebaseConfig');

if (!serializedFirebaseConfig) {
throw new Error('Firebase Config object not found in service worker query string.');
}

firebaseConfig = JSON.parse(serializedFirebaseConfig);
console.log("Service worker installed with Firebase config", firebaseConfig);
});

self.addEventListener("fetch", (event) => {
const { origin } = new URL(event.request.url);
if (origin !== self.location.origin) return;
event.respondWith(fetchWithFirebaseHeaders(event.request));
});

async function fetchWithFirebaseHeaders(request) {
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const installations = getInstallations(app);
const headers = new Headers(request.headers);
const [authIdToken, installationToken] = await Promise.all([
getAuthIdToken(auth),
getToken(installations),
]);
headers.append("Firebase-Instance-ID-Token", installationToken);
if (authIdToken) headers.append("Authorization", `Bearer ${authIdToken}`);
const newRequest = new Request(request, { headers });
return await fetch(newRequest);
}

async function getAuthIdToken(auth) {
await auth.authStateReady();
if (!auth.currentUser) return;
return await getIdToken(auth.currentUser);
}
1 change: 0 additions & 1 deletion nextjs-end/functions/.gitignore

This file was deleted.

19 changes: 0 additions & 19 deletions nextjs-end/functions/index.js

This file was deleted.

23 changes: 0 additions & 23 deletions nextjs-end/functions/package.json

This file was deleted.

24 changes: 12 additions & 12 deletions nextjs-end/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,19 @@
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"build": "npm run build-service-worker && next build",
"build-service-worker": "npx esbuild auth-service-worker.js --bundle --outfile=public/auth-service-worker.js",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@google-cloud/firestore": "^6.7.0",
"firebase": "^10.3.1",
"firebase-admin": "^11.10.1",
"next": "13.4.10",
"protobufjs": "^7.2.5",
"react": "18.2.0",
"react-dom": "18.2.0",
"request": "^2.88.2"
},
"devDependencies": {
"encoding": "^0.1.13"
"@google/generative-ai": "^0.10.0",
"firebase": "^10.11.1",
"firebase-admin": "^12.1.0",
"next": "^14.2.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"server-only": "^0.0.1"
},
"browser": {
"fs": false,
Expand All @@ -28,5 +25,8 @@
"child_process": false,
"net": false,
"tls": false
},
"devDependencies": {
"esbuild": "^0.20.2"
}
}
9 changes: 4 additions & 5 deletions nextjs-end/src/app/actions.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
"use server";

import { addReviewToRestaurant } from "@/src/lib/firebase/firestore.js";
import { getAuthenticatedAppForUser } from "@/src/lib/firebase/firebase";
import { getAuthenticatedAppForUser } from "@/src/lib/firebase/serverApp.js";
import { getFirestore } from "firebase/firestore";

// This is a next.js server action, an alpha feature, so
// use with caution
// This is a Server Action
// 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);
const { firebaseServerApp } = await getAuthenticatedAppForUser();
const db = getFirestore(firebaseServerApp);

await addReviewToRestaurant(db, data.get("restaurantId"), {
text: data.get("text"),
Expand Down
10 changes: 5 additions & 5 deletions nextjs-end/src/app/layout.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import "@/src/app/styles.css";
import Header from "@/src/components/Header.jsx";
import { getAuthenticatedAppForUser } from "@/src/lib/firebase/firebase";
import { getAuthenticatedAppForUser } from "@/src/lib/firebase/serverApp";
// Force next.js to treat this route as server-side rendered
// Without this line, during the build process, next.js will treat this route as static and build a static HTML file for it
export const dynamic = "force-dynamic";
Expand All @@ -13,15 +13,15 @@ export const metadata = {


export default async function RootLayout({ children }) {
const { currentUser } = await getAuthenticatedAppForUser();
const { currentUser } = await getAuthenticatedAppForUser();
return (
<html lang="en">

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

<main>{children}</main>
</body>
<main>{children}</main>
</body>

</html>
);
Expand Down
5 changes: 4 additions & 1 deletion nextjs-end/src/app/page.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import RestaurantListings from "@/src/components/RestaurantListings.jsx";
import { getRestaurants } from "@/src/lib/firebase/firestore.js";
import { getAuthenticatedAppForUser } from "@/src/lib/firebase/serverApp.js";
import { getFirestore } from "firebase/firestore";

// Force next.js to treat this route as server-side rendered
// Without this line, during the build process, next.js will treat this route as static and build a static HTML file for it
Expand All @@ -12,7 +14,8 @@ export const dynamic = "force-dynamic";
export default async function Home({ searchParams }) {
// Using seachParams which Next.js provides, allows the filtering to happen on the server-side, for example:
// ?city=London&category=Indian&sort=Review
const restaurants = await getRestaurants(searchParams);
const {firebaseServerApp} = await getAuthenticatedAppForUser();
const restaurants = await getRestaurants(getFirestore(firebaseServerApp), searchParams);
return (
<main className="main__home">
<RestaurantListings
Expand Down
58 changes: 31 additions & 27 deletions nextjs-end/src/app/restaurant/[id]/page.jsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,37 @@
import Restaurant from "@/src/components/Restaurant.jsx";
import { useUser } from "@/src/lib/firebase/auth";
import { Suspense } from "react";
import { getRestaurantById } from "@/src/lib/firebase/firestore.js";
import { getAuthenticatedAppForUser, getAuthenticatedAppForUser as getUser } from "@/src/lib/firebase/serverApp.js";
import ReviewsList, {
ReviewsListSkeleton,
} from "@/src/components/Reviews/ReviewsList";
import {
getRestaurantById,
getReviewsByRestaurantId,
} from "@/src/lib/firebase/firestore.js";
import {
getAuthenticatedAppForUser
} from "@/src/lib/firebase/firebase.js";



export const dynamic = "force-dynamic";
GeminiSummary,
GeminiSummarySkeleton,
} from "@/src/components/Reviews/ReviewSummary";
import { getFirestore } from "firebase/firestore";

export default async function Home({ params }) {
// This is a server component, we can access URL
// parameters via Next.js and download the data
// we need for this page
const restaurant = await getRestaurantById(params.id);
const reviews = await getReviewsByRestaurantId(params.id);
const { currentUser } = await getAuthenticatedAppForUser()
const { currentUser } = await getUser();
const {firebaseServerApp} = await getAuthenticatedAppForUser();
const restaurant = await getRestaurantById(getFirestore(firebaseServerApp), params.id);

return (
<main className="main__restaurant">
<Restaurant
id={params.id}
initialRestaurant={restaurant}
initialReviews={reviews}
initialUserId={currentUser?.uid || ""}
/>
</main>
);
return (
<main className="main__restaurant">
<Restaurant
id={params.id}
initialRestaurant={restaurant}
initialUserId={currentUser?.uid || ""}
>
<Suspense fallback={<GeminiSummarySkeleton />}>
<GeminiSummary restaurantId={params.id} />
</Suspense>
</Restaurant>
<Suspense
fallback={<ReviewsListSkeleton numReviews={restaurant.numRatings} />}
>
<ReviewsList restaurantId={params.id} userId={currentUser?.uid || ""} />
</Suspense>
</main>
);
}
Loading

0 comments on commit ddc901f

Please sign in to comment.