Connect Firebase To Next.js

Connect Firebase To Next.js

14 min read · 2757 words · Shared April 15, 2021 by

Article Summary: Learn how to connect Next.js to Google's Firebase and use authentication, cloud Firestore, Realtime Database, and cloud storage.

Introduction

In this tutorial we will add Firebase to a Next.js website. If you are using another react based framework, the process should be very similar and you should get something out of this tutorial.

We will first connect the website to Firebase (which is a step in itself), and then we'll add the following functionalities in the following order:

  • Authentication
  • Cloud Firestore
  • Realtime Database
  • Storage

Initialize Firebase

First, make sure you have a Firebase project. You can create one here if you do not have one.

Create a folder called firebase and inside of that folder create a file called initFirebase.js. We will add all our firebase related functionality inside of this folder.

Now, install the 2 dependancies:

yarn add firebase firebase-admin

Import these inside of initFirebase.js.

If you haven't already, create a web app inside of your Firebase project. Once you go through those steps, you will be able to download your credentials. From here, create a file called .env and paste in the secrets. Your file should look like mine (Except with your secret variables obviously!).

NEXT_PUBLIC_FIREBASE_API_KEY=
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=
NEXT_PUBLIC_FIREBASE_DATABASE_URL=
NEXT_PUBLIC_FIREBASE_PROJECT_ID=
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=
NEXT_PUBLIC_FIREBASE_APP_ID=

# for firebase-admin
FIREBASE_CLIENT_EMAIL=
FIREBASE_PRIVATE_KEY=

We can now use these secrets to initialize Firebase. Your initFirebase file should look like this:

initFirebase.js
import firebase from 'firebase/app'
// the below imports are option - comment out what you don't need
import 'firebase/auth'
import 'firebase/firestore'
import 'firebase/storage'
import 'firebase/analytics'
import 'firebase/performance'

const clientCredentials = {
    apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
    authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
    databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL,
    projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
    storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
    messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
    appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
}

Lastly, let's export a function called initFirebase() that uses these credentials to initialize Firebase!

initFirebase.js
export default function initFirebase() {
    if (!firebase.apps.length) {
        firebase.initializeApp(clientCredentials)
        // Check that `window` is in scope for the analytics module!
        if (typeof window !== 'undefined') {
            // Enable analytics. https://firebase.google.com/docs/analytics/get-started
            if ('measurementId' in clientCredentials) {
                firebase.analytics()
                firebase.performance()
            }
        }
        console.log('Firebase was successfully init.')
    }
}

Now that Firebase is initialized, we can begin to use it.

Cloud Firestore

We will start off with cloud Firestore.

Write

Create a folder named components. Inside of this folder, create a folder called cloudFirestore. All of our cloud Firestore related components will go inside of here. Inside of here, create a file named write.js. In here we will:

  1. Import Firebase
  2. Import cloud Firestore

Then, create a basic React component named WriteToCloudFirestore. Your file should look like this:

Write.js
import firebase from 'firebase/app'
import 'firebase/firestore'

const WriteToCloudFirestore = () => {
    return (
        <div style={{ margin: '5px 0' }}>

        </div>
    )
}

export default WriteToCloudFirestore

To send data, we need to create a function sendData. We will then invoke this function on a button press. We'll wrap it in a try catch loop as well.

Write.js
import firebase from 'firebase/app'
import 'firebase/firestore'

const WriteToCloudFirestore = () => {
    const sendData = () => {
        try {
            // write data here
        } catch (error) {
            console.log(error)
            alert(error)
        }
    }

    return (
        <div style={{ margin: '5px 0' }}>
            <button onClick={sendData}>Send Data To Cloud Firestore</button>
        </div>
    )
}

export default WriteToCloudFirestore

To write data, we access the firestore method on the firebase object. We grab the collection called myCollection(can be any name you like), we grad the document called my_document(can be any name you like), and then use the set() method. It looks something like this:

Write.js
firebase
    .firestore()
    .collection('myCollection')
    .doc('my_document') // leave as .doc() for a random unique doc name to be assigned
    .set({
        string_data: 'Benjamin Carlson',
        number_data: 2,
        boolean_data: true,
        map_data: { stringInMap: 'Hi', numberInMap: 7 },
        array_data: ['text', 4],
        null_data: null,
        time_stamp: firebase.firestore.Timestamp.fromDate(new Date('December 17, 1995 03:24:00')),
        geo_point: new firebase.firestore.GeoPoint(34.714322, -131.468435)
    })
    .then(alert('Data was successfully sent to cloud firestore!'))

When we put it all together it looks like this:

Write.js
import firebase from 'firebase/app'
import 'firebase/firestore'

const WriteToCloudFirestore = () => {
    const sendData = () => {
        try {
            firebase
                .firestore()
                .collection('myCollection')
                .doc('my_document') // leave as .doc() for a random unique doc name to be assigned
                .set({
                    string_data: 'Benjamin Carlson',
                    number_data: 2,
                    boolean_data: true,
                    map_data: { stringInMap: 'Hi', numberInMap: 7 },
                    array_data: ['text', 4],
                    null_data: null,
                    time_stamp: firebase.firestore.Timestamp.fromDate(new Date('December 17, 1995 03:24:00')),
                    geo_point: new firebase.firestore.GeoPoint(34.714322, -131.468435)
                })
                .then(alert('Data was successfully sent to cloud firestore!'))
        } catch (error) {
            console.log(error)
            alert(error)
        }
    }

    return (
        <button onClick={sendData}>Send Data To Cloud Firestore</button>
    )
}

export default WriteToCloudFirestore

Imort this component inside of index.js and press the button. You should see a success alert and if you navigate to Firbase you should see the data.

Write Success Image
View Full Image

·

Write Success Image

Read

Now let's read this data. Create a file named Read.js in the same folder as Write.js. Make a component named ReadDataFromCloudFirestore(). Inside of it create a button, with an onClick event which invokes a function named readData. Your file should look like this:

Read.js
import firebase from 'firebase/app'
import 'firebase/firestore'

const ReadDataFromCloudFirestore = () => {
    const readData = () => {

    }

    return (
        <button onClick={readData}>Read Data From Cloud Firestore</button>
    )
}

export default ReadDataFromCloudFirestore

Now, inside of the method lets read the data by invoking the firestore() method off the firebase object. Select the collection and the document the same way as before. Next, use the onSnapshot method. Inide of it, create a function and console.log the data. The data is inside of doc.data(). The doc object is the entire response. Here is the complete file:

Read.js
import firebase from 'firebase/app'
import 'firebase/firestore'

const ReadDataFromCloudFirestore = () => {
    const readData = () => {
        try {
            firebase
                .firestore()
                .collection('myCollection')
                .doc('my_document')
                .onSnapshot(function (doc) {
                    console.log(doc.data())
                })
            alert('Data was successfully fetched from cloud firestore! Close this alert and check console for output.')
        } catch (error) {
            console.log(error)
            alert(error)
        }
    }

    return (
        <button onClick={readData}>Read Data From Cloud Firestore</button>
    )
}

export default ReadDataFromCloudFirestore

Import this function into index.js and you'll see your data in the console!

Authentication

Create a folder called Auth inside of your components folder. Inside of there, create a file named FirebaseAuth.js. We will be using the sign in pop up flow from the ract firebase ui library. We'll start by importing what we need.

FirebaseAuth.js
import initFirebase from '../../firebase/initFirebase'
import { useEffect, useState } from 'react'
import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth'
import firebase from 'firebase/app'
import 'firebase/auth'
import { setUserCookie } from '../../firebase/userCookies'
import { mapUserData } from '../../firebase/mapUserData'

Note, we have not created the setUserCookie or setUserCookie files yet.

You might notice that we are importing initFirebase when we already have it in the index.js file. This is because it makes more sense to init Firebase in the auth flow rather than the index page because if you are building out a large application, the user will not always start at the index page.

Next, paste the code below:

FirebaseAuth.js
initFirebase() // initialize firebase

const firebaseAuthConfig = {
    signInFlow: 'popup',
    // Auth providers
    // https://github.com/firebase/firebaseui-web#configure-oauth-providers
    signInOptions: [
        {
            provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
            requireDisplayName: false,
        },
        // add additional auth flows below
        firebase.auth.GoogleAuthProvider.PROVIDER_ID,
        firebase.auth.TwitterAuthProvider.PROVIDER_ID,
        firebase.auth.GithubAuthProvider.PROVIDER_ID,
    ],
    signInSuccessUrl: '/',
    credentialHelper: 'none',
    callbacks: {
        signInSuccessWithAuthResult: async ({ user }, redirectUrl) => {
            const userData = mapUserData(user)
            setUserCookie(userData)
        },
    },
}

That is how easy it is to create a popup auth flow. You will notice that I provide multiple sign in options and set user data and cookies, which again, we have yet to implement.

Finally, create the actual component and return this config inside of the pop up auth flow that we imported from react ui.

FirebaseAuth.js
const FirebaseAuth = () => {
    // Do not SSR FirebaseUI, because it is not supported.
    // https://github.com/firebase/firebaseui-web/issues/213
    const [renderAuth, setRenderAuth] = useState(false)
    useEffect(() => {
        if (typeof window !== 'undefined') {
            setRenderAuth(true)
        }
    }, [])
    return (
        <div>
            {renderAuth ? (
                <StyledFirebaseAuth
                    uiConfig={firebaseAuthConfig}
                    firebaseAuth={firebase.auth()}
                />
            ) : null}
        </div>
    )
}

export default FirebaseAuth

Inside of our pages folder, create a file named auth.js and add the component we just created.

auth.js
import FirebaseAuth from '../components/auth/FirebaseAuth'

const Auth = () => {
    return (
        <div>
            <div>
                <FirebaseAuth />
                <p><a href="/">Go Home</a></p>
            </div>
        </div>
    )
}

export default Auth

While we are in the pages folder, remove the initFirebase line from index.js as well.

Auth Logic

The only stuff left to do now is handle the logic. Create 4 files inside of your firebases folder.

  • mapUserData.js
  • useUser.js
  • userCookies.js

Cookies

Paste in the following code into userCookies.js. This is taken from the next.js documentation.

userCookies.js
import cookies from 'js-cookie'

export const getUserFromCookie = () => {
    const cookie = cookies.get('auth')
    if (!cookie) {
        return
    }
    return JSON.parse(cookie)
}

export const setUserCookie = (user) => {
    cookies.set('auth', user, {
        // firebase id tokens expire in one hour
        // set cookie expiry to match
        expires: 1 / 24,
    })
}

export const removeUserCookie = () => cookies.remove('auth')

This code sets cookies so the user can close the website and reopen it and not have to log back in. Make sure to add js-cookie via yarn or npm.

User Data

Next, inside of map user data, add the following:

mapUserData.js
export const mapUserData = (user) => {
    const { uid, email, xa, displayName, photoUrl } = user
    return {
        id: uid,
        email,
        token: xa,
        name: displayName,
        profilePic: photoUrl
    }
}

This again is taken from the Next.js website. The code maps the data we are getting to out custom variables.

Custom User Hook

Finally, let's modify the useUser file. This file has the following tasks:

  • create a log out method.
  • gives us a hook to get the logged in user anywhere in out app.
  • provides an auth listener

Here is the code:

useUser.js
import { useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import firebase from 'firebase/app'
import 'firebase/auth'
import initFirebase from '../firebase/initFirebase'
import {
    removeUserCookie,
    setUserCookie,
    getUserFromCookie,
} from './userCookies'
import { mapUserData } from './mapUserData'

initFirebase()

const useUser = () => {
    const [user, setUser] = useState()
    const router = useRouter()

    const logout = async () => {
        return firebase
            .auth()
            .signOut()
            .then(() => {
                // Sign-out successful.
                router.push('/auth')
            })
            .catch((e) => {
                console.error(e)
            })
    }

    useEffect(() => {
        // Firebase updates the id token every hour, this
        // makes sure the react state and the cookie are
        // both kept up to date
        const cancelAuthListener = firebase.auth().onIdTokenChanged((user) => {
            if (user) {
                const userData = mapUserData(user)
                setUserCookie(userData)
                setUser(userData)
            } else {
                removeUserCookie()
                setUser()
            }
        })

        const userFromCookie = getUserFromCookie()
        if (!userFromCookie) {
            router.push('/')
            return
        }
        setUser(userFromCookie)

        return () => {
            cancelAuthListener()
        }
    }, [])

    return { user, logout }
}

export { useUser }

The auth should now work. Try to log in with email and password. Note, if you want to use any of the OAuth providers you will need to turn them on in your firebase auth settings page.

Now that we have auth, we want to dynamically show a custom index page based on if the user is logged in or not. Make the following changes to index.js.

index.js
import Head from 'next/head'
import styles from '../styles/Home.module.css'
import WriteToCloudFirestore from '../components/cloudFirestore/Write'
import ReadDataFromCloudFirestore from '../components/cloudFirestore/Read'
import { useUser } from '../firebase/useUser'

export default function Home() {
  const { user, logout } = useUser()

  if (user) {
    return (
      <div>
        <h1>{user.name}</h1>
        <h3>{user.email}</h3>
        {user.profilePic ? <image src={user.profilePic} height={50} width={50}></image> : <p>No profile pic</p>}
        <WriteToCloudFirestore />
        <ReadDataFromCloudFirestore />
        <button onClick={() => logout()}>Log Out</button>
      </div>
    )
  }

  else return (
    <div className={styles.container}>
      <p><a href="/auth">Log In!</a></p>

      <Head>
        <title>Create Next App</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>
          Welcome to <a href="https://nextjs.org">Next.js!</a>
        </h1>

 // the rest is the same

Updating Read/Write

Finally, let's change the document name when we send data via cloud firestore. This way, each user has their data stored in a unique key.

In cloudFirestore folder:

Read.js
import firebase from 'firebase/app'
import 'firebase/firestore'
import { useUser } from '../../firebase/useUser'

const ReadDataFromCloudFirestore = () => {
    const { user } = useUser()
    const readData = () => {
        try {
            firebase
                .firestore()
                .collection('myCollection')
                .doc(user.id)
                .onSnapshot(function (doc) {
                    console.log(doc.data())
                })
            alert('Data was successfully fetched from cloud firestore! Close this alert and check console for output.')
        } catch (error) {
            console.log(error)
            alert(error)
        }
    }

    return (
        <button onClick={readData}>Read Data From Cloud Firestore</button>
    )
}

export default ReadDataFromCloudFirestore
Write.js
import firebase from 'firebase/app'
import 'firebase/firestore'
import { useUser } from '../../firebase/useUser'

const WriteToCloudFirestore = () => {
    const { user } = useUser()
    const sendData = () => {
        try {
            firebase
                .firestore()
                .collection('myCollection')
                .doc(user.id) // leave as .doc() for a random unique doc name to be assigned
                .set({
                    string_data: 'Benjamin Carlson',
                    number_data: 2,
                    boolean_data: true,
                    map_data: { stringInMap: 'Hi', numberInMap: 7 },
                    array_data: ['text', 4],
                    null_data: null,
                    time_stamp: firebase.firestore.Timestamp.fromDate(new Date('December 17, 1995 03:24:00')),
                    geo_point: new firebase.firestore.GeoPoint(34.714322, -131.468435)
                })
                .then(alert('Data was successfully sent to cloud firestore!'))
        } catch (error) {
            console.log(error)
            alert(error)
        }
    }

    return (
        <button onClick={sendData}>Send Data To Cloud Firestore</button>
    )
}

export default WriteToCloudFirestore

That is it for Firebase authentication!

Realtime Database

Next, we'll add realtime database. We will be building a simmple button where when you press is, it increments a count. This count is stored in the database and is unique to each user. The count will then be fetched back and displayed to the user.

First, create a component named Counter.js inside of the components/realtimeDatabase folder. Create a basic component.

Counter.js
import { useState, useEffect } from 'react'
import firebase from 'firebase/app'
import 'firebase/database'

const Counter = ({ id }) => {
    return (
        <button onClick={increaseCount}>Increase count {count ? count : '–––'}</button>
    )
}

export default Counter

As you can see, the button is called a method named increaseCount. Let's create this. To do this, we will use the firebase.database().ref().child().off() syntax.

Counter.js
import { useState, useEffect } from 'react'
import firebase from 'firebase/app'
import 'firebase/database'

const Counter = ({ id }) => {
    const [count, setCount] = useState('')
    useEffect(() => {
        const onCountIncrease = (count) => setCount(count.val())

        const fetchData = async () => {
            firebase.database().ref('counts').child(id).on('value', onCountIncrease)
        }

        fetchData()

        return () => {
            firebase.database().ref('counts').child(id).off('value', onCountIncrease)
        }
    }, [id])

    const increaseCount = async () => {
        const registerCount = () => fetch(`/api/incrementCount?id=${encodeURIComponent(id)}`)
        registerCount()
    }

    return (
        <button onClick={increaseCount}>Increase count {count ? count : '–––'}</button>
    )
}

export default Counter

We ned to create the api route for this.

Create a file named fetchCount.js inside of pages/api.

Inside it we can fetch the count.

fetchCount.js
import firebase from 'firebase/app'
import 'firebase/database'

export default (req, res) => {
    const ref = firebase.database().ref('counts').child(req.query.id)

    return ref.once('value', (snapshot) => {
        res.status(200).json({
            total: snapshot.val()
        })
    })
}

Now, lets import the component inside index.js and use it! Remember to pass in a unique id; I will be using the users uid from useUser.

index.js
import Counter from '../components/realtimeDatabase/Counter'
// other imports

// other code above
<ReadDataFromCloudFirestore />
<Counter id={user.id} />  // added line
<button onClick={() => logout()}>Log Out</button>
// other code below

Storage

For storage, we only need to create one file. Inside of your components folder, create a folder named storage and then create a file named uploadFile.js. First create a component with html input and progress.

uploadFile.js
import { useRef, useState } from 'react'
import firebase from 'firebase/app'
import 'firebase/storage'

const UploadFile = () => {
    const inputEl = useRef(null)
    const [value, setValue] = useState(0)
    return (
        <>
            <progress value={value} max="100"></progress>
            <input
                type="file"
                onChange={uploadFile}
                ref={inputEl}
            />
        </>
    )
}

As you can see, we are using useState and useRef to handle the procress indicator and the file upload. To upload the file, we need to get the file, and use .put to send it to firebase.

uploadFile.js
function uploadFile() {
        // get file
        var file = inputEl.current.files[0]

        // create a storage ref
        var storageRef = firebase.storage().ref('user_uploads/' + file.name)

        // upload file
        var task = storageRef.put(file)

        // update progress bar
        task.on('state_change',

            function progress(snapshot) {
                setValue((snapshot.bytesTransferred / snapshot.totalBytes) * 100)
            },

            function error(err) {
                alert(error)
            },

            function compleete() {
                alert('Uploaded to firebase storage successfully!')
            }
        )
    }

Finally, import this component into index.js!

index.js
import ReadDataFromCloudFirestore from '../components/cloudFirestore/Read'
import { useUser } from '../firebase/useUser'
import Counter from '../components/realtimeDatabase/Counter'
import UploadFile from '../components/storage/UploadFile' // added line

export default function Home() {
  const { user, logout } = useUser()

  if (user) {
    return (
      <div>
      <div className={styles.container}>
        <h1>{user.name}</h1>
        <h3>{user.email}</h3>
        {user.profilePic ? <image src={user.profilePic} height={50} width={50}></image> : <p>No profile pic</p>}
        <WriteToCloudFirestore />
        <ReadDataFromCloudFirestore />
        <Counter id={user.id} />
        <UploadFile /> // added line
        <button onClick={() => logout()}>Log Out</button>
      </div>
      // other code
    )

Conclusion

With that, we have successfully added Firebase to our Next.js website.

Edit on GitHub

Related Tags

    #firebase

Related Posts

Add Algolia's InstantSearch to Next.js - A Quickstart · December 17, 2021
Build A Todo Web App Using Next.js, Firebase, Chakra UI + React Hooks · September 26, 2021
How To Use Firebase As Flutter's Backend · May 3, 2021

On This Page

Introduction

Initialize Firebase

Cloud Firestore

Write

Read

Authentication

Popup Auth Flow

Auth Logic

Cookies

User Data

Custom User Hook

Updating Read/Write

Realtime Database

Storage

Conclusion

View Related Posts

December 17, 2021

algolia.png

Add Algolia's InstantSearch to Next.js - A Quickstart

September 26, 2021

firebase.png

Build A Todo Web App Using Next.js, Firebase, Chakra UI + React Hooks

May 3, 2021

firebase.png

How To Use Firebase As Flutter's Backend


Loading author data...

    Legal

    Terms

    Disclaimer

    Privacy Policy


    Carlson Technologies Logo© Copyright 2021 - 2024, Carlson Technologies LLC. All Rights Reserved.