DEV Community

Cover image for How I Built a Blog with Next.js and Firebase
Ankur Tyagi
Ankur Tyagi

Posted on • Originally published at theankurtyagi.com

How I Built a Blog with Next.js and Firebase

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.

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[];
}
Enter fullscreen mode Exit fullscreen mode

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

// 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}`;
};

Enter fullscreen mode Exit fullscreen mode

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

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

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

Enter fullscreen mode Exit fullscreen mode

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.

"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>
    );
}
Enter fullscreen mode Exit fullscreen mode

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.

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

Enter fullscreen mode Exit fullscreen mode

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.

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

Enter fullscreen mode Exit fullscreen mode

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

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

Enter fullscreen mode Exit fullscreen mode

Image description

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.

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

Enter fullscreen mode Exit fullscreen mode

Image description

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.

"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>
    );
}
Enter fullscreen mode Exit fullscreen mode

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.

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

Enter fullscreen mode Exit fullscreen mode

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 and sign in with a Gmail account.

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

Image description

Provide your app name and register it.

Image description

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.

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 };

Enter fullscreen mode Exit fullscreen mode

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

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

Image description

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

Image description

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

Image description

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

Image description

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:

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

Enter fullscreen mode Exit fullscreen mode

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.

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}`
            );
        });
};

Enter fullscreen mode Exit fullscreen mode

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.

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}`
            );
        });
};
Enter fullscreen mode Exit fullscreen mode

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.

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>;
}
Enter fullscreen mode Exit fullscreen mode

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:

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("/");
    }
};
Enter fullscreen mode Exit fullscreen mode

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.

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

Enter fullscreen mode Exit fullscreen mode

Image description

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.

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

Enter fullscreen mode Exit fullscreen mode

Fetching existing posts

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

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>;
}
Enter fullscreen mode Exit fullscreen mode

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.

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.");
    }
};
Enter fullscreen mode Exit fullscreen mode

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.

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

Top comments (1)

Collapse
 
pk001 profile image
PK

this is a solid guide. thanks