Next.js: Firebase library and example problems

Created on 9 Sep 2018  路  4Comments  路  Source: vercel/next.js

Bug report

Trying to implement the Firebase library into a NextJS project seems quite difficult.

I sometimes get the following error:

__WEBPACK_IMPORTED_MODULE_0_firebase_app__.messaging

But the most common ones are the ones I'll explain here.

Here's my config:

import * as firebase from "firebase/app";
import "firebase/firestore";
import "firebase/storage";
import "firebase/messaging";

const config = {
  ...
};

const db = !firebase.apps.length
  ? firebase.initializeApp(config).firestore()
  : firebase.app().firestore();
db.settings({ timestampsInSnapshots: true });
if (typeof window !== "undefined") {
  db.enablePersistence().catch(function(err) {
    if (err.code == "failed-precondition") {
      console.log("failed-precondition");
    } else if (err.code == "unimplemented") {
      console.log("unimplemented");
    }
  });
}

const storage = firebase.storage();
const messaging = firebase.messaging();

export { db, storage, messaging };

Describe the bug

the firestore db variable works fine.

But just initializing the messaging variable gives me the following error and crashes the app:

ReferenceError: self is not defined
    at isSupported (/path/to/project/node_modules/@firebase/messaging/dist/index.cjs.js:2093:5)
    at Object.factoryMethod [as messaging] (/path/to/project/node_modules/@firebase/messaging/dist/index.cjs.js:2074:14)
    at FirebaseAppImpl._getService (/path/to/project/node_modules/@firebase/app/dist/index.node.cjs.js:138:66)
    at FirebaseAppImpl.(anonymous function) [as messaging] (/path/to/project/node_modules/@firebase/app/dist/index.node.cjs.js:327:31)
    at Object.serviceNamespace [as messaging] (/path/to/project/node_modules/@firebase/app/dist/index.node.cjs.js:312:32)
    at Object../lib/firebase.config.ts (/path/to/project/.next/server/bundles/pages/_app.js:124:72)
    at __webpack_require__ (/path/to/project/.next/server/bundles/pages/_app.js:23:31)
    at Object../pages/_app.tsx (/path/to/project/.next/server/bundles/pages/_app.js:147:79)
    at __webpack_require__ (/path/to/project/.next/server/bundles/pages/_app.js:23:31)
    at Object.0 (/path/to/project/.next/server/bundles/pages/_app.js:541:18)
    at __webpack_require__ (/path/to/project/.next/server/bundles/pages/_app.js:23:31)
    at /path/to/project/.next/server/bundles/pages/_app.js:70:18
    at Object.<anonymous> (/path/to/project/.next/server/bundles/pages/_app.js:71:12)
    at Module._compile (internal/modules/cjs/loader.js:689:30)
    at Module.m._compile (/path/to/project/node_modules/ts-node/src/index.ts:439:23)
    at Module._extensions..js (internal/modules/cjs/loader.js:700:10)

If I try to use the storage via getInitialProps aka server side I get the following error:

/path/to/project/node_modules/@firebase/storage/dist/index.cjs.js:672
        this.xhr_ = new XMLHttpRequest();
                        ^
ReferenceError: XMLHttpRequest is not defined

Only happens on server side though.

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:
1.

yarn create next-app --example with-firebase-hosting-and-typescript with-firebase-hosting-and-typescript-app
  1. yarn add firebase
  2. add this config file with your firebase variables:
import * as firebase from "firebase/app";
import "firebase/firestore";
import "firebase/storage";
import "firebase/messaging";

const config = {
  apiKey: "replace",
  authDomain: "replace",
  databaseURL: "replace",
  projectId: "replace",
  storageBucket: "replace",
  messagingSenderId: "replace"
};

const db = !firebase.apps.length
  ? firebase.initializeApp(config).firestore()
  : firebase.app().firestore();
db.settings({ timestampsInSnapshots: true });
if (typeof window !== "undefined") {
  db.enablePersistence().catch(function(err) {
    if (err.code == "failed-precondition") {
      console.log("failed-precondition");
    } else if (err.code == "unimplemented") {
      console.log("unimplemented");
    }
  });
}

const storage = firebase.storage();
// const messaging = firebase.messaging();

export { db, storage };

Note I'm commenting messaging because otherwise it would crash the app with the self is not defined

  1. Change the index file to this:
import * as React from "react";
import App from "../components/App";
import { storage } from "../initFirebase";

class Index extends React.Component {
  static async getInitialProps() {
    console.log(await storage.ref("path/to/file.jpg").getDownloadURL());
  }
  render() {
    return (
      <App>
        <p>Index Page</p>
      </App>
    );
  }
}

export default Index;
  1. yarn dev go to localhost:3000 and you'll get the referenceError: XMLHttpRequest is not defined error

Expected behavior

Should work as expected.

System information

  • OS: [macOS]
  • Browser: Chrome
  • Version of Next.js: "next": "^6.1.2"
good first issue

Most helpful comment

  static async getInitialProps() {
    console.log("server");
    console.log(await storage.ref("path/to/image.jpg").getDownloadURL());
    return {};
  }

The issue here is that you are using the firebase/storage package on the server side of the application.

firebase/storage relies upon XMLHttpRequest packaged with the browser which doesn't exist in a NodeJS environment. Here is the firebase-js-sdk ticket.

if you move the getDownloadURL() call to the client side it should work.

  async componentDidMount() {
    console.log("client");
    console.log(await storage.ref("path/to/image.jpg").getDownloadURL());
  }

The same thing is occurring with the firebase/messaging package. firebase.messaging() is intended for client-side use only. The time at which your lib/firebase.config.js is being evaluated is a server-side NodeJS environment, not the required Browser environment. I would suggest either poly-filling as mentioned in that ticket or doing something like this:

// lib/firebase.config.js
const storage = firebase.messaging();
const setupMessaging = () => {
  return firebase.messaging();
};

export { storage, setupMessaging};
// page/index.js
  async componentDidMount() {
    console.log("client");
    setupMessaging();
    console.log(await storage.ref("path/to/image.jpg").getDownloadURL());
  }

The reason that the two packages error at a different time is because of the dependencies are different and appear at different parts of the API. It seems messaging requires the browsers global or something in it's constructor whereas storage requires the browsers XMLHttpRequest in getDownloadURL().

I may work on a more comprehensive example elsewhere (setting up messaging is difficult at the best of times) with all the standard firebase features. Hope this helped.

All 4 comments

@jthegedus could you have a look

hey @timneutkens I made a very simple repo replicating the bug:
https://github.com/johhansantana/firebase-nextjs-error

Just install dependencies and run it

  static async getInitialProps() {
    console.log("server");
    console.log(await storage.ref("path/to/image.jpg").getDownloadURL());
    return {};
  }

The issue here is that you are using the firebase/storage package on the server side of the application.

firebase/storage relies upon XMLHttpRequest packaged with the browser which doesn't exist in a NodeJS environment. Here is the firebase-js-sdk ticket.

if you move the getDownloadURL() call to the client side it should work.

  async componentDidMount() {
    console.log("client");
    console.log(await storage.ref("path/to/image.jpg").getDownloadURL());
  }

The same thing is occurring with the firebase/messaging package. firebase.messaging() is intended for client-side use only. The time at which your lib/firebase.config.js is being evaluated is a server-side NodeJS environment, not the required Browser environment. I would suggest either poly-filling as mentioned in that ticket or doing something like this:

// lib/firebase.config.js
const storage = firebase.messaging();
const setupMessaging = () => {
  return firebase.messaging();
};

export { storage, setupMessaging};
// page/index.js
  async componentDidMount() {
    console.log("client");
    setupMessaging();
    console.log(await storage.ref("path/to/image.jpg").getDownloadURL());
  }

The reason that the two packages error at a different time is because of the dependencies are different and appear at different parts of the API. It seems messaging requires the browsers global or something in it's constructor whereas storage requires the browsers XMLHttpRequest in getDownloadURL().

I may work on a more comprehensive example elsewhere (setting up messaging is difficult at the best of times) with all the standard firebase features. Hope this helped.

thanks @jthegedus

I may work on a more comprehensive example elsewhere (setting up messaging is difficult at the best of times) with all the standard firebase features.

This would be nice if you have the time.

I don't think firebase seems to be the right tool to use with nextjs giving how hacky one has to be in order for it to work. I'll look for a different solution.

Thanks!

Was this page helpful?
0 / 5 - 0 ratings