6

I'm stuck on making firebase work in my gatsby application that uses Redux with Redux-sagas. I know about the existence of firebase-sagas but I'm trying to make without using it.

I'm trying to init firebase auth by:

import * as firebase from 'firebase/app';
import 'firebase/auth';

export const app = firebase.initializeApp(
  {
    apiKey        : "apiKey",
    authDomain    : "project.firebaseapp.com",
    databaseURL   : "https://project.firebaseio.com",
    projectId     : "project",
    storageBucket : "project.appspot.com",
    appId         : "appId"
  }

)

export const authRef = () => app.auth(); //also tried: firebase.auth() and firebase.auth(app)
//firebase.auth returns a function, but firebase.auth() throws error

I have the following config on my gatsby-node.js:

const path = require('path');

exports.onCreateWebpackConfig = ({ actions, plugins, loaders, getConfig }) => {
  const config = getConfig()

  config.resolve = {
    ...config.resolve,
      mainFields: ['module', 'browser', 'main'],
      alias: {
        ...config.resolve.alias,
        ['firebase/app']       : path.resolve(__dirname, 'node_modules/firebase/app/dist/index.cjs.js'),
        ['firebase/auth']      : path.resolve(__dirname, 'node_modules/firebase/auth/dist/index.cjs.js'),
      }
  }

  actions.replaceWebpackConfig(config)
}

It trows the error:

{ [M [Error]: The XMLHttpRequest compatibility library was not found.]
  code: 'auth/internal-error',
  message: 'The XMLHttpRequest compatibility library was not found.' }

I think it's some problem related to webpack. I would love any insights on this problem :)

Felipe César
  • 1,234
  • 2
  • 16
  • 34

1 Answers1

17

As Gatsby builds pages in a server environment, you can't access Firebase during Gatsby build time. Firebase calls (using the Web SDK) have to happen when the user is on a browser/client environment.

One solution to this problem is creating a function like so:

firebase.js:

import firebase from '@firebase/app';
import '@firebase/auth';
import '@firebase/firestore';
import '@firebase/functions';

const config = {
   ... firebase config here
};

let instance;

export default function getFirebase() {
  if (typeof window !== 'undefined') {
    if (instance) return instance;
    instance = firebase.initializeApp(config);
    return instance;
  }

  return null;
}

This file returns a function, which returns an instance of Firebase if the user has the global window available (e.g. on the browser). It also caches the Firebase instance to ensure it cannot be reinitialised again (in case of the user changing page on your website).

In your components, you can now do something similar to the following:

import getFirebase from './firebase';

function MyApp() {
  const firebase = getFirebase();
}

As Gatsby will try to build this page into HTML during gatsby build, the firebase const will return null, which is correct, as the Firebase Web SDK cannot initialise on a server environment. However, to make use of Firebase on your website, you need to wait until Firebase is available (so the user has to have loaded your website), so we can make use of Reacts useEffect hook:

import React { useEffect } from 'react';    
import getFirebase from './firebase';

function MyApp() {
  const firebase = getFirebase();

  useEffect(() => {
    if (!firebase) return;

    firebase.auth().onAuthStateChanged((user) => { ... });
  }, [firebase]);
}

This works as Firebase is being used in a browser environment and has access to the browser, which is needed for the Web SDK to work.

It does have drawbacks; your compo have to return null in instances when you need Firebase to display content, which will mean your HTML build on the server will not contain any HTML, and it'll be injected via the client. In most cases though, such as an account page, this is fine.

If you need access to data from say Cloud Firestore to display page content, you're best using the Admin SDK to fetch content and add it to GraphQL during Gatsby build. That way it will be available on the server during build time.

Sorry if that was a waffle or not clear!

Elliot Hesp
  • 503
  • 3
  • 8
  • 1
    Thank you very much Elliot. You really helped me understand how it works and find a solution. I was able to adapt what you said to my context, I just have two considerations: -instead of using `@firebase` I'm using `firebase` -I had to adapt one the function a little bit: `if (instance) { //updated this part instance = firebase.initializeApp(config); return instance }` – Felipe César Oct 07 '19 at 08:37
  • 1
    @FelipeCésar no problem, glad it helped. I updated my answer with the instance fix/change. Also with the Firebase imports, I believe this is down to which version of the SDK you use, but very similar regardless :) – Elliot Hesp Oct 07 '19 at 08:56
  • 1
    This doesn't seem to work with Firebase 8.0.2, it will throw `fetch not defined` when building the project with `gatsby build`. However, it works while running the project with `gatsby develop` – Daniel Guldberg Aaes Nov 18 '20 at 18:18