How I Built a Blog with Next.js and Firebase
Tags: react, javascript, nextjs, programming
Published: 2024-05-08 09:31:27

## Introduction

Building full-stack web applications has never been this easy.

You don't need to write backend and frontend code to create fully functional web applications that authenticate users, store data, and upload files to cloud storage. Firebase allows you to build these applications easily within a few minutes.

In this tutorial, you'll learn how to build a blog that allows anyone to read posts and create an account as post authors to write posts, comment, and interact with other users.
The application uses Firebase Google authentication methods to sign users into the application and Firebase Firestore to perform CRUD operations when users create, delete, and comment on posts.

> PS: A basic knowledge of React/Next.js is required to fully understand this article.

## What is Firebase?

Firebase is a Backend-as-a-Service (Baas) owned by Google that enables us to build full-stack web applications in a few minutes. Services like Firebase make it very easy for front-end developers to build full-stack web applications with little or no backend programming skills.

Firebase provides various authentication methods, a **NoSQL database**, a **real-time database**, **file storage**, **cloud functions**, **hosting services**, and many more.

---

## Why you should use Firebase?

Firebase is an exceptional platform that empowers developers to build full-stack applications effortlessly. Let's explore some unique features that set Firebase apart from other Backend as a Service (BaaS) platforms.

**Complete Authentication System:**

Firebase offers a complete authentication system that allows you to easily manage user authentication in your application. With Firebase Authentication, you can authenticate users using email/password, phone numbers, Google, Facebook, and more.

**Cloud Firestore**

Firebase Firestore is a flexible, scalable database for mobile, web, and server development. It allows you to store and sync data between your users in real-time, making it ideal for applications that require CRUD operations and complex data queries.

**File Storage**
Firebase provides an efficient Cloud Storage that enables you to upload and download files easily within your application. It seamlessly integrates with Firebase Authentication and Firebase Firestore, making it easy to build complex applications that requires serving and retrieving various file formats.

**Hosting**
Firebase provides hosting services that allow you to deploy web apps and static content with a single command. It provides SSL encryption, global CDN delivery, and continuous deployment, ensuring fast and reliable performance for your users.

---

## Building the application interface with Next.js

Here, I'll walk you through creating the various components and pages within the blogging application.

The application is divided into four pages: 

- Home page
- Login page 
- Create Post page
- Post Slug page that displays the posts' content

Before we proceed, create a `types.ts` file at the root of your Next.js project to define the attributes of the posts and comments within the application.

```typescript
export interface Comment {
    id: string;
    content: string;
    author_name: string;
    pub_date: string;
}
export interface Post {
    post_id: string;
    author_id: string;
    title: string;
    content: string;
    author_name: string;
    image_url: string;
    pub_date: string;
    slug: string;
    comments: Comment[];
}
```

Next, create a `utils.ts` file within the Next.js app folder and copy the code snippet below into the file:

```typescript
// Within the utils.ts file
export const getCurrentDate = (): string => {
    const currentDate = new Date();
    const day = currentDate.getDate();
    const monthIndex = currentDate.getMonth();
    const year = currentDate.getFullYear();
    const suffixes = ["th", "st", "nd", "rd"];
    const suffix = suffixes[(day - 1) % 10 > 3 ? 0 : (day - 1) % 10];
    const months = [
        "Jan.",
        "Feb.",
        "Mar.",
        "Apr.",
        "May",
        "Jun.",
        "Jul.",
        "Aug.",
        "Sept.",
        "Oct.",
        "Nov.",
        "Dec.",
    ];
    const month = months[monthIndex];
    return `${day}${suffix} ${month} ${year}`;
};
//👇🏻 Create a unique slug from posts' titles
export const slugifySentences = (sentence: string): string => {
    // Remove special characters and replace spaces with hyphens
    const slug = sentence
        .toLowerCase()
        .replace(/[^a-z0-9\s-]/g, "")
        .replace(/\s+/g, "-");
    // Generate 5 random letters
    const randomLetters = Array.from({ length: 5 }, () =>
        String.fromCharCode(97 + Math.floor(Math.random() * 26))
    ).join("");
    return `${slug}-${randomLetters}`;
};

```

The `getCurrentDate` function accepts the current date as a parameter and returns it in a more readable format, suitable for use within blog posts. The `slugifySentences` function creates a slug from each post's title.

### The Home Page

The application home page displays all the available posts and allow users to read their preferred posts.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1fajsexqs0wl460w5iaf.png)

Copy the code snippet below into the `app/page.tsx` file.

```typescript
"use client";
import Image from "next/image";
import Nav from "./components/Nav";
import Link from "next/link";
import { useEffect, useState } from "react";
export default function Home() {
    const [posts, setPosts] = useState<Post[]>();
    const shortenText = (text: string): string => {
        return text.length <= 55 ? text : text.slice(0, 55) + "...";
    };
    useEffect(() => {
        fetchAllPosts();
    }, []);
    const fetchAllPosts = async () => {
        // 👉🏻 fetch all posts
    };
    return (
        <div>
            <Nav />
            <main className='p-8 w-full flex lg:flex-row flex-col items-center gap-5 flex-wrap justify-center'>
                {posts?.map((post) => (
                    <Link
                        href={`/posts/${post.slug}`}
                        className='cursor-pointer lg:w-1/3 rounded-lg w-full border-2 h-[400px] bg-white'
                        key={post.post_id}
                    >
                        <Image
                            src={post.image_url}
                            alt='Image'
                            width={300}
                            height={300}
                            className='w-full h-2/3 rounded-t-lg'
                        />
                        <section className='h-1/3 w-full p-4 flex items-center'>
                            <p className='font-semibold text-xl text-blue-500'>
                                {shortenText(post.title)}
                            </p>
                        </section>
                    </Link>
                ))}
            </main>
        </div>
    );
}

```

The code snippet above fetches all the existing blog posts and displays their titles and cover images. 

There is also a Nav component that displays the blog title and the Sign In button.

Create a `components` folder containing the `Nav` component within the Next.js app folder and update it as shown below.

```typescript
"use client";
import Link from "next/link";
import { useEffect, useState } from "react";
import { handleSignOut } from "../utils";
import { usePathname } from "next/navigation";

export default function Nav() {
    const [displayName, setDisplayName] = useState<string>("");
    const pathname = usePathname();

    useEffect(() => {
        if (localStorage.getItem("user")) {
            const user = JSON.parse(localStorage.getItem("user") || "");
            setDisplayName(user.displayName);
        }
    }, []);

    return (
        <nav className='w-full py-4 border-b-2 px-8 text-center flex items-center justify-between sticky top-0 bg-[#f5f7ff]'>
            <Link href='/' className='text-2xl font-bold'>
                Blog
            </Link>
            {displayName ? (
                <div className='flex items-center gap-5'>
                    <p className='text-sm font-semibold'>{displayName}</p>
                    {pathname !== "/posts/create" && (
                        <Link href='/posts/create' className='underline text-blue-500'>
                            Create Post
                        </Link>
                    )}
                    <button
                        className='bg-red-500 text-white px-6 py-3 rounded-md'
                        onClick={handleSignOut}
                    >
                        Log Out
                    </button>
                </div>
            ) : (
                <Link
                    href='/login'
                    className='bg-blue-500 text-white px-8 py-3 rounded-md'
                >
                    Sign in
                </Link>
            )}
        </nav>
    );
}
```

The Nav component checks if the user is authenticated to determine which buttons to display. If the user is not authenticated, it renders the Sign-in button; otherwise, it displays the user's name, a Create Post button, and a Log Out button.

### The Login Page

The Login page simply displays a Google sign-in button that enables users to log in using their Gmail account.

To create the Login page, first, create a login folder within the Next.js app folder. Inside the login folder, create two files: `layout.tsx` and `page.tsx`.

```bash
cd app
mkdir login && cd login
touch layout.tsx page.tsx

```
Copy the code snippet below into the `page.tsx` file. It renders a button that enables users to sign in to the application using their Gmail account.

```typescript
"use client";
import { useState } from "react";

export default function Nav() {
    const [loading, setLoading] = useState<boolean>(false);

    const handleGoogleSignIn = () => {
        setLoading(true);
        //👉🏻 handle Firebase Google authentication
    };

    return (
        <div className='w-full min-h-screen flex flex-col items-center justify-center'>
            <h2 className='font-bold text-2xl mb-6'>Sign into Blog</h2>
            <button
                className='w-1/3 border-2 border-gray-600 mb-6 rounded-md hover:bg-black hover:text-white px-8 py-4'
                disabled={loading}
                onClick={handleGoogleSignIn}
            >
                {loading ? "Signing in..." : "Sign in with Google"}
            </button>
        </div>
    );
}

```
Update the `layout.tsx` file as shown below. It changes the page's title.

```typescript
import type { Metadata } from "next";
import { Sora } from "next/font/google";
const inter = Sora({ subsets: ["latin"] });

export const metadata: Metadata = {
    title: "Log in | Blogging App",
    description: "Generated by create next app",
};

export default function RootLayout({
    children,
}: Readonly<{
    children: React.ReactNode;
}>) {
    return (
        <html lang='en'>
            <body className={inter.className}>{children}</body>
        </html>
    );
}

```


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iqoomeme5i2l5067j3n1.png)


### The Posts pages

First, create a posts folder containing a `create` folder and a `[..slug]` folder within the Next.js app folder.

The create folder adds a `/posts/create` client route to the application, and the `[..slug]` folder creates a dynamic route for each blog post.

Next, create a `page.tsx` file within the `/posts/create` folder and add a form that enables users to enter the details of a blog post.

```typescript
"use client";
import { useEffect, useState } from "react";
import Nav from "@/app/components/Nav";
import { getCurrentDate, slugifySentences } from "@/app/utils";

export default function CreatePost() {
    const [coverPhoto, setCoverPhoto] = useState<string>("");
    const [content, setContent] = useState<string>("");
    const [title, setTitle] = useState<string>("");
    const [uploading, setUploading] = useState<boolean>(false);

    const handleFileReader = (e: React.ChangeEvent<HTMLInputElement>) => {
        const reader = new FileReader();
        if (e.target.files && e.target.files[0]) {
            reader.readAsDataURL(e.target.files[0]);
        }
        reader.onload = (readerEvent) => {
            if (readerEvent.target && readerEvent.target.result) {
                setCoverPhoto(readerEvent.target.result as string);
            }
        };
    };

    const handleCreatePost = async (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        setUploading(true);
        console.log({
            title,
            content,
            author_name: userData.displayName,
            pub_date: getCurrentDate(),
            slug: slugifySentences(title),
            comments: [],
        });
        //👉🏻 save blog post to the database
    };
    return (
        <div>
            <Nav />
            <main className='md:px-8 py-8 px-4 w-full'>
                <form className='flex flex-col w-full' onSubmit={handleCreatePost}>
                    <label htmlFor='title' className='text-sm text-blue-600'>
                        Title
                    </label>
                    <input
                        type='text'
                        name='title'
                        id='title'
                        value={title}
                        required
                        onChange={(e) => setTitle(e.target.value)}
                        className=' px-4 py-3 border-2 rounded-md text-lg mb-4'
                    />
                    <label htmlFor='content' className='text-sm text-blue-600'>
                        Content
                    </label>
                    <textarea
                        name='content'
                        rows={15}
                        value={content}
                        required
                        onChange={(e) => setContent(e.target.value)}
                        id='content'
                        className=' px-4 py-3 border-2 rounded-md mb-4'
                    ></textarea>
                    <label htmlFor='upload' className='text-sm text-blue-600'>
                        Upload Cover Photo
                    </label>
                    <input
                        type='file'
                        name='upload'
                        id='upload'
                        required
                        onChange={handleFileReader}
                        className=' px-4 py-3 border-2 rounded-md mb-4'
                        accept='image/jpeg, image/png'
                    />
                    <button
                        type='submit'
                        className='bg-blue-600 mt-4 text-white py-4 rounded-md'
                        disabled={uploading}
                    >
                        {uploading ? "Creating post..." : "Create Post"}
                    </button>
                </form>
            </main>
        </div>
    );
}

```


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gquwkjl9fg9ymd7hbfxm.png)



The `[...slug]` page displays the contents of a blog post. Add a `page.tsx` file within the folder and copy the code snippet below into the file.

```typescript
"use client";
import Comments from "@/app/components/Comments";
import Nav from "@/app/components/Nav";
import { extractSlugFromURL } from "@/app/utils";
import { usePathname, useRouter } from "next/navigation";
import { useEffect, useState } from "react";

export default function Post() {
    const params = usePathname();
    const router = useRouter();
    const slug = extractSlugFromURL(params);
    const [loading, setLoading] = useState<boolean>(true);
    const [post, setPost] = useState<Post | null>(null);
    return (
        <div>
            <Nav />
            <main className='w-full md:p-8 px-4'>
                <header className='mb-6 py-4'>
                    <h2 className='text-3xl text-blue-700 font-bold mb-2'>
                        {post?.title}
                    </h2>
                    <div className='flex'>
                        <p className='text-red-500 mr-8 text-sm'>
                            Author: <span className='text-gray-700'>{post?.author_name}</span>
                        </p>
                        <p className='text-red-500 mr-6 text-sm'>
                            Posted on: <span className='text-gray-700'>{post?.pub_date}</span>
                        </p>
                    </div>
                </header>
                <div>
                    <p className=' text-gray-700 mb-3'>{post?.content}</p>
                </div>
            </main>
            <Comments comments={post?.comments} post_id={post?.post_id} />
        </div>
    );
}
```

The code snippet above displays the post's title, author, published date, and content, and passes the post's comments into a `Comments` component.

Finally, create a `Comments` component that accepts a post's comments as props and renders the available comments, including an input field for adding new comments.

```typescript
"use client";
import Link from "next/link";
import React, { useEffect, useState } from "react";
import { getCurrentDate } from "../utils";
export const generateCommentID = () =>
    Math.random().toString(36).substring(2, 10);

export default function Comments({
    comments,
    post_id,
}: {
    comments: Comment[] | undefined;
    post_id: string | undefined;
}) {
    const [user, setUser] = useState({ displayName: "", u_id: "" });
    const [newComment, setNewComment] = useState<string>("");
    const [postingComment, setPostingComment] = useState<boolean>(false);
    useEffect(() => {
        if (localStorage.getItem("user")) {
            const user = JSON.parse(localStorage.getItem("user") || "");
            setUser(user);
        }
    }, []);
    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        // 👉🏻 postComment();
    };
    if (!user.displayName)
        return (
            <main className='p-8 mt-8'>
                <h3 className=' font-semibold'>
                    You need to{" "}
                    <Link href='/login' className='text-blue-500 underline'>
                        sign in
                    </Link>{" "}
                    before you can comment on this post
                </h3>
            </main>
        );
    return (
        <main className='p-8 mt-8'>
            <h3 className='font-semibold text-xl mb-4 text-blue-500'>Comments</h3>
            <form onSubmit={handleSubmit} className='mb-8'>
                <textarea
                    className='w-full p-4 border border-gray-300 rounded-md mb-2'
                    placeholder='Leave a comment'
                    value={newComment}
                    required
                    onChange={(e) => setNewComment(e.target.value)}
                />
                <button
                    className='bg-blue-500 text-white px-4 py-2 rounded-md'
                    disabled={postingComment}
                >
                    {postingComment ? "Posting..." : "Post Comment"}
                </button>
            </form>
            {comments && comments.length > 0 && (
                <h3 className='font-semibold text-xl mb-4 text-blue-500'>
                    Recent comments
                </h3>
            )}
            <div className='w-full flex items-start md:flex-row flex-col justify-center gap-4 flex-wrap'>
                {comments?.map((comment) => (
                    <div
                        className='bg-white p-4 rounded-lg md:w-1/4 w-full shadow-lg'
                        key={comment.id}
                    >
                        <p className='mb-4'>{comment.content}</p>
                        <div>
                            <h4 className='font-semibold text-sm'>{comment.author_name}</h4>
                            <p className='text-gray-500 text-sm'>{comment.pub_date}</p>
                        </div>
                    </div>
                ))}
            </div>
        </main>
    );
}

```
The code snippet above accepts the post's comments as props and renders them within the component. However, it ensures that only authenticated users can create and view existing comments. Unauthenticated users can only read a blog post.
Congratulations! You've completed the user interface for the application.

---

## How to add Firebase to a Next.js app

In this section, you'll learn how to set up Firebase project and add it to a Next.js application. A Firebase project has all the features provided by Firebase and enables you to use them within your software applications.

First, you need to visit the [Firebase console](https://console.firebase.google.com/u/0/?pli=1) and sign in with a Gmail account.

Create a Firebase project and select the </> icon to create a new Firebase web app.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y151j5vcmtgf79tcrg0z.png)

Provide your app name and register it.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hkqyefs7vpuzwzx49hni.png)


Install the Firebase SDK in your Next.js project by running the code snippet below.

`npm install firebase
`
Next, Create a firebase.ts file at the root of your Next.js project and copy the Firebase configuration code for your app into the file.

```javascript
import { initializeApp, getApps } from "firebase/app";
import { getFirestore } from "firebase/firestore";
import { getAuth, GoogleAuthProvider } from "firebase/auth";
import { getStorage } from "firebase/storage";
// Your web app's Firebase configuration
const firebaseConfig = {
    apiKey: "***********",
    authDomain: "***********",
    projectId: "**************",
    storageBucket: "**********",
    messagingSenderId: "************",
    appId: "********************",
};
//👇🏻 Initialize Firebase
const app =
    getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];
//👇🏻 required functions
const db = getFirestore(app);
const auth = getAuth(app);
const storage = getStorage(app);
const googleProvider = new GoogleAuthProvider();
//👇🏻 required exports
export { db, auth, googleProvider, storage };

```
The code snippet above sets up Firebase in your Next.js application. It initializes Firebase Firestore, authentication, and storage instances, allowing you to access these features within the application.
Before you can start using these features, you'll need to set them up.

### Configuring the Firebase features

For this application, you'll need to set up Firebase Storage for saving cover images for posts, Firebase Firestore for storing post contents, and Firebase Authentication for user authentication.

To get started, select Build from the left-hand panel, then click on Firestore Database to create a database.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/24rg47bx2ffhk38ootbr.png)


Create the database in test mode, and pick your closest region.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vxtlfm2907cih4otuixm.png)

Next, select Storage from the sidebar menu to set up the cloud storage for the files within the application.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r08cghz2mrm8lnm7kq05.png)


Finally, select Authentication and pick Google from the list of authentication providers.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g2zukkcymq8ofxg83joz.png)

Select the support email for the Firebase project and click Save to enable Google authentication.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lneofypnwzjzg7n734fa.png)

Congratulations. You can start saving files to the Firebase Cloud Storage, interacting with the Firebase Firestore for CRUD operations, and authenticating users via Firebase Google Authentication.

---

## Handling user authentication within the application

Here, you'll learn how to sign users in and out of the application and how to retrieve the current user details from Firebase.

Within the `login/page.tsx` file, update the `handleGoogleSignIn` function as done below:

```javascript
"use client";
import { useState } from "react";
import { handleSignIn } from "../utils";
import { googleProvider } from "../../../firebase";
import { GoogleAuthProvider } from "firebase/auth";

export default function Nav() {
    const [loading, setLoading] = useState<boolean>(false);
    const handleGoogleSignIn = () => {
        setLoading(true);
        handleSignIn(googleProvider, GoogleAuthProvider);
    };
    return (
        <div className='w-full min-h-screen flex flex-col items-center justify-center'>
            <h2 className='font-bold text-2xl mb-6'>Sign into Blog</h2>
            <button
                className='w-1/3 border-2 border-gray-600 mb-6 rounded-md hover:bg-black hover:text-white px-8 py-4'
                disabled={loading}
                onClick={handleGoogleSignIn}
            >
                {loading ? "Signing in..." : "Sign in with Google"}
            </button>
        </div>
    );
}

```
The code snippet above triggers the `handleSignIn` function when a user clicks the Sign in button.

Add the `handleSignIn` function to the `utils.ts` file. It redirects the user to the Google authentication page to enable the application to access the user's profile details.

```javascript
import { signInWithPopup } from "firebase/auth";
import { auth } from "../../firebase";
export const handleSignIn = (provider: any, authProvider: any) => {
    signInWithPopup(auth, provider)
        .then((result) => {
            const credential = authProvider.credentialFromResult(result);
            const token = credential?.accessToken;
            //👉🏻 sign in successful
            if (token) {
                const user = result.user;
                //👉🏻 log user's details to the console
                console.log({ displayName: user.displayName, u_id: user.uid });
                window.location.replace("/");
            }
        })
        .catch((error) => {
            console.log({ error: error.message });
            alert(
                `Refresh page! An error occurred while signing out - ${error.message}`
            );
        });
};

```
To sign users out of the application, create a `handleSignOut` function within the `utils.ts` file and copy the code snippet below into the file. It logs the current user out of the application.

```javascript
import { signOut } from "firebase/auth";
import { auth } from "../../firebase";
export const handleSignOut = () => {
    signOut(auth)
        .then(() => {
            window.location.replace("/");
        })
        .catch((error) => {
            alert(
                `Refresh page! An error occurred while signing out - ${error.message}`
            );
        });
};
```


### Protecting pages from unauthenticated users

Firebase allows us to determine whether the current user is signed in or not. This can be helpful when you need to protect certain pages from unauthenticated users, such as the Post creation page.

Add the code snippet below to the `posts/create/page.tsx` file. It listens to the authentication state of the signed-in user and updates the current view of the application based on the authentication status.

```javascript
import { onAuthStateChanged } from "firebase/auth";
const [userData, setUserData] = useState<any>({});
export default function PostCreate() {
    useEffect(() => {
        onAuthStateChanged(auth, (user) => {
            user ? setUserData(user) : router.back();
        });
    }, [router]);
    return <div>{/** -- UI elements --*/}</div>;
}
```

---

## How to save and retrieve posts from Firebase Firestore

Here, you'll learn how to save and retrieve from Firebase, and also how to update each posts when a user adds a comment on a post.

### Creating new posts

Update the `handleCreatePost` function within the `posts/create/page.tsx` file as done below:

```javascript
import { addDoc, collection, updateDoc, doc } from "firebase/firestore";
import { auth, db, storage } from "../../../../firebase";
import { getCurrentDate, slugifySentences } from "@/app/utils";
import { ref, uploadString, getDownloadURL } from "firebase/storage";

const handleCreatePost = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setUploading(true);
    //👇🏻 add post to Firebase Firestore
    const docRef = await addDoc(collection(db, "posts"), {
        author_id: userData.uid,
        title,
        content,
        author_name: userData.displayName,
        pub_date: getCurrentDate(),
        slug: slugifySentences(title),
        comments: [],
    });
    //👇🏻 creates a storage reference using the docRef id
    const imageRef = ref(storage, `posts/${docRef.id}/image`);

    if (coverPhoto) {
        await uploadString(imageRef, coverPhoto, "data_url").then(async () => {
            //👇🏻 Gets the image URL
            const downloadURL = await getDownloadURL(imageRef);
            //👇🏻 Updates the docRef, by adding the logo URL to the document
            await updateDoc(doc(db, "posts", docRef.id), {
                image_url: downloadURL,
            });
        });

        setUploading(false);
        alert("Post created successfully!");
        router.push("/");
    }
};
```


Now, let me explain the function:

The code snippet below adds the author's ID and name, the post's title, content, published date, and slug to a `posts` collection within the database.

```javascript
const docRef = await addDoc(collection(db, "posts"), {
    author_id: userData.uid,
    title,
    content,
    author_name: userData.displayName,
    pub_date: getCurrentDate(),
    slug: slugifySentences(title),
    comments: [],
});

```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ph1oppgotuzkppvrl9es.png)



After saving the post's details to Firebase Firestore, the function uploads the post's cover image to Firebase Cloud Storage and updates the posts collection with a new attribute (`image_url`) containing the URL of the uploaded cover image.

```javascript
// creates a storage reference using the docRef id
const imageRef = ref(storage, `posts/${docRef.id}/image`);
if (coverPhoto) {
    await uploadString(imageRef, coverPhoto, "data_url").then(async () => {
        //👇🏻 Gets the uploaded image URL
        const downloadURL = await getDownloadURL(imageRef);
        //👇🏻 Updates the docRef, by adding the cover image URL to the document
        await updateDoc(doc(db, "posts", docRef.id), {
            image_url: downloadURL,
        });
    });
}

```
### Fetching existing posts

To fetch all the saved posts from Firebase, add the code snippet below within the `app/page.tsx` file.

```javascript
import { collection, getDocs } from "firebase/firestore";
import { useEffect, useState } from "react";
import { db } from "../../firebase";
export default function Home() {
    const [posts, setPosts] = useState<Post[]>();
    useEffect(() => {
        fetchAllPosts();
    }, []);
    const fetchAllPosts = async () => {
        const querySnapshot = await getDocs(collection(db, "posts"));
        const posts: any = [];
        querySnapshot.forEach((doc) => {
            posts.push({ ...doc.data(), post_id: doc.id });
        });
        setPosts(posts);
    };
    return <div>{/** -- UI elements --*/}</div>;
}
```

The code snippet above retrieves the saved posts from Firebase and displays them within the application.

### Adding comments to posts

To add comments to a post, update the `postComment` function with the `Comments.tsx` file to fetch the particular post and add the newly created comment to the `comments` array attribute.

```javascript
import { getCurrentDate } from "../utils";
import { doc, updateDoc, arrayUnion } from "firebase/firestore";
import { db } from "../../../firebase";
//👇🏻 generate comment ID
export const generateCommentID = () => {
    return Math.random().toString(36).substring(2, 10);
};
const postComment = async (post_id: string) => {
    try {
        const postRef = doc(db, "posts", post_id);
        await updateDoc(postRef, {
            comments: arrayUnion({
                id: generateCommentID(),
                content: newComment,
                author_name: user.displayName,
                pub_date: getCurrentDate(),
            }),
        });
        alert("Comment posted successfully");
    } catch (err) {
        console.log(err);
        alert("An error occurred while posting your comment. Please try again.");
    }
};
```

Congratulations. You've completed the tutorial.

## Conclusion

So far! You’ve learned the following:

- what is Firebase
- how to add Firebase to a Next.js app
- how to work with Firebase Auth, Storage, and Database
- how to build a blog app with Next.js and Firebase

The source for this tutorial is available [here](https://github.com/tyaga001/a-blog-app-with-Next.js-and-Firebase).

Thank you for reading and If you enjoyed this blog and want to learn more, check out my other articles.

- [The Good Parts of React](https://www.freecodecamp.org/news/learn-react-key-concepts/)
- [Everything about React Hooks](https://theankurtyagi.com/react-hooks/)
- [Learn How to Use and Validate Forms in React](https://theankurtyagi.com/how-to-validate-forms-in-react/)
- [How to Build a Task Management App with React and Appwrite](https://theankurtyagi.com/appwrite/)
- [How to Build a Notes App Using React and Supabase](https://theankurtyagi.com/notes-app-react-supabase/)