DEV Community

Rodolphe Dupuis
Rodolphe Dupuis

Posted on

Building a Full-Stack App with Next.js, and Firebase: A Step-by-Step Guide

In today’s fast-paced tech world, choosing the right stack is essential for entrepreneurs and startups eager to bring their ideas to life quickly.
The combination of Next.js, and Firebase provides a powerful way to rapidly prototype and build apps, making it the ideal environment to test out your new ideas at a very important pace.
With Next.js’s flexible, component-based front end, and Firebase’s all-in-one cloud services like authentication, database, and hosting, this stack makes it easy to develop proof-of-concept (PoC) projects and iterate on ideas fast—without complex infrastructure setup.

In this guide, we’ll build a simple to-do app to show how quickly you can go from concept to working application.

What You’ll Build in This Guide

We’ll build a simple task management application where users can:

  • Sign up and sign in using Firebase Authentication.
  • Create and manage tasks (CRUD operations) stored in Firebase Firestore.
  • The app will be deployed on Vercel, making it production-ready.

Prerequisites

Here is a quick list about the things to have before going any further:

  • A Firebase account.
  • Basic knowledge of React and Next.js.
  • Node.js installed on your machine.

Step 1: Setting up the Next.js project

Open a terminal and write the command below:

npx create-next-app task-manager
cd task-manager
Enter fullscreen mode Exit fullscreen mode

This creates a Next.js application inside the folder task-manager.

Install Firebase SDK

Install the necessary Firebase packages using the command below:

npm install firebase
Enter fullscreen mode Exit fullscreen mode

The Firebase SDK will allow us to interact with the services included like Firestore and Authentication from within the Next.js app.


Step 2: Firebase Setup

Create a Firebase project

  • Go to the Firebase console and create a new Firebase project.
  • Enable Firebase Authentication and Firestore Database.

Configure Firebase Authentication

In the Authentication section, add a new sign-in method using the native email/password provider.
In the Firestore Database section, create a new database and start in test mode for development purposes.

Download Firebase Config

To connect your app to Firebase, you'll need to configure Firebase in your Next.js app.

Go to Project Settings in Firebase Console, then under Your Apps, select Web App to get the Firebase configuration.

Copy this configuration as you'll use it to initialize Firebase in your app.


Step 3: Initialize Firebase in the Next.js app

Create Firebase Configuration file

Create a file called firebase.js inside the src/ folder or wherever you prefer.

import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
import 'firebase/auth';
import { getAuth } from 'firebase/auth';

const firebaseConfig = {
  apiKey: 'YOUR_API_KEY',
  authDomain: 'YOUR_AUTH_DOMAIN',
  projectId: 'YOUR_PROJECT_ID',
  storageBucket: 'YOUR_STORAGE_BUCKET',
  messagingSenderId: 'YOUR_MESSAGING_SENDER_ID',
  appId: 'YOUR_APP_ID',
};

const app = initializeApp(firebaseConfig);

const db = getFirestore(app);
const auth = getAuth(app);

export { db, auth };
Enter fullscreen mode Exit fullscreen mode

Replace the placeholders with your Firebase configuration copied in the previous step.


Step 4: Build the Authentication using Firebase

In this step, we will build the authentication part where users can sign up, sign in, and sign out.

Sign-up and Sign-in pages

Create the pages/signUp.js for the signing page:

import { useState } from 'react';
import { auth } from '../firebase';
import { useRouter } from 'next/router';
import { createUserWithEmailAndPassword } from 'firebase/auth';

const Signup = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState(null);
  const router = useRouter();

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
        await createUserWithEmailAndPassword(auth, email, password);
        router.push('/tasks'); // Redirect to tasks page after signup
    } catch (err) {
      setError(err.message);
    }
  };

  return (
    <div>
      <h2>Sign Up</h2>
      <form onSubmit={handleSubmit}>
        <input 
          type="email" 
          placeholder="Email" 
          value={email} 
          onChange={(e) => setEmail(e.target.value)} 
        />
        <input 
          type="password" 
          placeholder="Password" 
          value={password} 
          onChange={(e) => setPassword(e.target.value)} 
        />
        <button type="submit">Sign Up</button>
      </form>
      {error && <p>{error}</p>}
    </div>
  );
};

export default Signup;
Enter fullscreen mode Exit fullscreen mode

Create the pages/signIn.js for the signing page:

import { useState } from 'react';
import { auth } from '../firebase';
import { useRouter } from 'next/router';
import { signInWithEmailAndPassword } from 'firebase/auth';

const Signin = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState(null);
  const router = useRouter();

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
        await signInWithEmailAndPassword(auth, email, password);
        router.push('/tasks'); // Redirect to tasks page after login
    } catch (err) {
      setError(err.message);
    }
  };

  return (
    <div>
      <h2>Sign In</h2>
      <form onSubmit={handleSubmit}>
        <input 
          type="email" 
          placeholder="Email" 
          value={email} 
          onChange={(e) => setEmail(e.target.value)} 
        />
        <input 
          type="password" 
          placeholder="Password" 
          value={password} 
          onChange={(e) => setPassword(e.target.value)} 
        />
        <button type="submit">Sign In</button>
      </form>
      {error && <p>{error}</p>}
    </div>
  );
};

export default Signin;
Enter fullscreen mode Exit fullscreen mode

Step5: Task management and CRUD operations with Firestore

This step is focusing on implementing the core of the application: the task management system.

Tasks Page with CRUD Operations

Create pages/tasks.js where users can add, view, and delete tasks:

import { useState, useEffect } from 'react';
import { db, auth } from '../firebase';
import { useRouter } from 'next/router';
import { collection, query, where, onSnapshot, addDoc, deleteDoc, doc } from 'firebase/firestore';

const Tasks = () => {
  const [tasks, setTasks] = useState([]);
  const [task, setTask] = useState('');
  const [error, setError] = useState(null);
  const router = useRouter();

  useEffect(() => {
    if (!auth.currentUser) {
      router.push('/signin');
      return;
    }

    const q = query(
      collection(db, 'tasks'),
      where('userId', '==', auth.currentUser.uid)
    );

    const unsubscribe = onSnapshot(q, (snapshot) => {
      const newTasks = snapshot.docs.map((doc) => {
        return { id: doc.id, ...doc.data() };
      });
      setTasks(newTasks);
    });

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

  const handleAddTask = async (e) => {
    e.preventDefault();
    if (task.trim()) {
      try {
        await addDoc(collection(db, 'tasks'), {
          userId: auth.currentUser.uid,
          task,
          completed: false,
        });
        setTask('');
      } catch (err) {
        setError(err.message);
      }
    }
  };

  const handleDeleteTask = async (id) => {
    try {
      await deleteDoc(doc(db, 'tasks', id));
    } catch (err) {
      setError(err.message);
    }
  };

  return (
    <div>
      <h2>Your Tasks</h2>
      <form onSubmit={handleAddTask}>
        <input
          type="text"
          value={task}
          onChange={(e) => setTask(e.target.value)}
          placeholder="Add a new task"
        />
        <button type="submit">Add Task</button>
      </form>
      {error && <p>{error}</p>}
      <ul>
        {tasks.map((task) => (
          <li key={task.id}>
            {task.task}
            <button onClick={() => handleDeleteTask(task.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default Tasks;
Enter fullscreen mode Exit fullscreen mode

This page allows users to:

  • Add tasks to Firebase Firestore.
  • View tasks associated with their user.
  • Delete tasks from Firestore.

Step 6: Deploying the app using Vercel

To deploy the app, you can use Vercel, which is built specifically for Next.js. Here’s how to deploy:

  1. Push your code to a GitHub repository.
  2. Go to Vercel, sign in with your **GitHub **account, and import the repository.
  3. Vercel **automatically detects that it’s a **Next.js project and handles the build and deployment process.

Conclusion

You’ve now built a full-stack application using Next.js and Firebase, covering essential features like authentication and CRUD operations with Firestore. This stack offers a powerful way to build scalable, serverless applications quickly, making it perfect for MVPs and production apps alike.

Please let me know if you have any problem going through this guide or if you see any possible improvement :)

Happy coding!

Top comments (0)