Firebase has kept a stable JavaScript interface for around 5 years now. If you wrote the following line of code 5 years ago, it would still work today.
import firebase from 'firebase/app'; import 'firebase/auth'; firebase.initializeApp({ /* config */ }); const auth = firebase.auth(); auth.onAuthStateChanged(user => { // Check for user status });
No one wants to rewrite code for the sake of rewriting code. A stable experience is one of the top decision factors when you choose to invest in a library. We have always taken that seriously. Our dedication to a stable API has been an ongoing balance of maintaining existing patterns and adopting new techniques for a better performance and developer experience. But, as Firebase lands more features, the SDK itself becomes larger. In order to reduce size and fit the modern web, we decided to make changes that required a break in our longstanding API.
A modular approach
When the original Firebase library was authored in 2012 the window was the only way to emulate a module system in the browser. It was a common practice to attach a "namespace" for your library on the window, hence window.firebase.
Today we have a native module system in the browser. We have a rich ecosystem of JavaScript module bundlers like Rollup and Webpack that make it easy to efficiently package application code with library code. These tools work best when dealing with module based JavaScript code.
The benefit is an effect called "tree shaking", which has the ability to eliminate unused code from your application and the libraries you import.
Firebase is changing to follow a modular pattern that provides tree shaking and therefore better performance for your sites. This modular approach removes "side-effect" imports and isolates features as individual functions.
Take a look at the sample below.
import { initializeApp } from 'firebase/app'; import { getAuth, onAuthStateChanged } from 'firebase/auth'; const firebaseApp = initializeApp({ /* config */ }); const auth = getAuth(firebaseApp); onAuthStateChanged(auth, user => { // Check for user status });
In the snippet above there's a lot of new pieces, especially around the imports, but there's also a lot of familiarity. The biggest difference is the organization of the code. It all comes down to namespaces versus modules.
Namespaces and services
Firebase has been available as a JavaScript module for quite some time now. However, our commitment to backwards compatibility and a stable API has kept us from taking advantage of a module first approach. It's one thing to be used as a module, but it's another to actually be modular. Any library can work in a module system, but it takes a specific organization to get the benefits of modules.
Firebase has followed a namespace and service pattern.
const firestore = firebase.firestore(); const colRef = firestore.ref('cities');
In this sample firebase is a namespace that contains the firestore service. Other services like Firestore, Authentication, Remote Config, Realtime Database, and Messaging can all also live on the namespace. Each service is also a namespace as well. The firestore service has a set of methods attached, like collection().
collection()
Organizing code in this way has its benefits. It's mentally easier for developers to "dot chain" to see what's available on a service. This approach was also easier to package before JavaScript had a bonafide module system. As JavaScript modules entered mainstream development, Firebase adapted but without breaking the namespace and service pattern. This kept the library stable but did not take full advantage of what JavaScript modules offer. Take the following code sample into account.
Let's go through the sample above, nearly line by line.
import firebase from 'firebase/app'; import 'firebase/auth';
The code starts by importing the firebase/app and firebase/auth packages. Notice though that they're imported differently. The firebase/app package has an export that gives us methods like initializeApp. However, the firebase/auth package has no exports. This type of import has many names, but I'm going to refer to it as a side-effect import. The side-effect import does not have any exports and typically when used they augment something. What does that mean for Firebase in this example? That's the firebase export.
firebase/app
firebase/auth
initializeApp
firebase.initializeApp({ /* config */ }); const auth = firebase.auth();
It's hard to tell what a side-effect import does knowing exactly what that importing firebase/auth does. In this case firebase/auth augments the firebase export from `firebase/app` and creates the `firebase.auth` namespace. If firebase/auth was not imported there would be an error when accessing firebase/auth.
firebase
The sample goes on to monitor a user's status.
auth.onAuthStateChanged(user => { // Check for user status });
The onAuthStateChanged method is available because of the side-effect import that augments the firebase export. But as a side-effect, the rest of the features offered by Firebase Authentication are on the namespace, whether you are using them or not. The current page may not handle any sign-in logic, but all 9 of Firebase Auth's sign-in methods will be included in your bundle. This is because of the namespace pattern.
onAuthStateChanged
The ability to chain methods off of the auth namespace is easy to understand and works well with IDEs that provide code completion like VSCode. It does not work well with tree shaking because no current tools are able to detect which methods on the chain are not used or what parts of a side-effect import are not needed. This leads to sites and apps that include more JavaScript than necessary. At Firebase we decided to reorganize our libraries in a modular pattern that supports tree shaking and therefore a smaller footprint in your site.
The new modular library
The new library moves away from the namespace approach and instead towards isolating features in JavaScript functions. Functions are a great way of organizing code and to promote tree shaking. Functions are independent units of code that take in arguments and return new values. Take a look at the new version of the sample code shown above.
The first thing to notice is that this sample is similar to one shown above. The first sample was eight lines of code, this sample is 8 lines of code. Both samples use two packages and accomplish the same objective: monitor authentication state.
Again, let's go through nearly line by line.
import { initializeApp } from 'firebase/app'; import { getAuth, onAuthStateChanged } from 'firebase/auth';
The side-effect imports are gone. The firebase/auth package provides exports rather than augmenting the firebase namespace. Another thing to note is there is no longer a firebase namespace. The firebase/app package does not return a "catch-all" export that contains all the methods from the package. Instead the package exports individual functions. Tree shaking tools like Rollup know that if a function isn't used it doesn't get included in the final build. This is unlike the firebase namespace or side-effect import in the previous sample. Build tools have to include everything when code is organized in that fashion.
The ergonomics of functions are different from a namespace with a bunch of methods attached to it. This is where the new organization really starts to show.
const firebaseApp = initializeApp({ /* config */ }); const auth = getAuth(firebaseApp);
The main difference in the lines above is that there is no more chaining from firebaseApp.auth(). Instead there is a getAuth() function that takes in firebaseApp and returns an auth instance. This may seem strange at first, but it provides more clarity than a side-effect import. Previously, the side-effect import augmented the firebase namespace behind the scenes. It was not clear how an auth service was created and it did not allow for tree shaking. The getAuth() function returns an initialized auth service from the details needed from the firebaseApp. This is a clear process: call a function with an argument, get a result back.
firebaseApp.auth()
getAuth()
firebaseApp
Creating a service this way allows the rest of the features of the library to be tree shake-able as well. Methods are no longer chained. Services are passed as the first argument and the function then uses the details of the auth service to do the rest. The rest of the functions in the firebase/auth package work this way as well. The auth service is the first argument and then what specific function needs next. Passing the auth service allows the other functions to use the details they need without needing a "catch-all" service that contains all the methods.
This new modular approach strips out unused code and builds upon modern web features.
Upgrade one library at a time
We fully understand that this upgrade is a breaking change which requires you to update existing code. As you would imagine, we have a lot of code internally that uses the Firebase JavaScript library as well. We understood this pain point immediately and created the compatibility library.
The compatibility library provides you the same API as the previous library version (version 8), but uses the new version 9 library under the hood. This allows you to use the new API and the old at the same time as you upgrade your site.
The ability to use these APIs side by side gives you flexibility when upgrading.
import firebase from 'firebase/compat/app'; import 'firebase/compat/auth'; const firebaseApp = firebase.initializeApp({ /* config */ }); const auth = firebaseApp.auth(); auth.onAuthStateChanged(user => { // Check for user status });
The first thing to notice is that the only change needed was to change the import paths to the /compat/ entry point. From here you can add the new libraries.
/compat/
import firebase from 'firebase/compat/app'; import { getAuth } from 'firebase/auth'; import 'firebase/compat/auth'; const firebaseApp = firebase.initializeApp({ /* config */ }); const auth = getAuth(firebaseApp); auth.onAuthStateChanged(user => { // Check for user status });
In the snippet above, both Firebase Auth library versions are being used. This code works and builds just fine. However, we don't recommend you use both versions unless you are in the middle of an upgrade.
import firebase from 'firebase/compat/app'; import { getAuth, onAuthStateChanged } from 'firebase/auth'; const firebaseApp = firebase.initializeApp({ /* config */ }); const auth = getAuth(firebaseApp); onAuthStateChanged(auth, user => { // Check for user status });
The last step you'll take when upgrading is to upgrade firebase/app. While upgrading you will need to keep the namespace import from the firebase/compat/app library until each Firebase service has been upgraded to the new version.
firebase/compat/app
import { initializeApp } from 'firebase/app'; import { getAuth } from 'firebase/auth'; const firebaseApp = initializeApp({ /* config */ }); const auth = getAuth(firebaseApp); onAuthStateChanged(auth, user => { // Check for user status });
Once everything is upgraded, you won't have any dependencies left on the compatibility library and you'll start to see the full tree shakeable benefits.
Let us know what you think!
We are really excited about the future of this library and the performance benefits it can provide. This library is still in beta and we want to know what you think of these changes. Come visit us on our GitHub discussion board and let us know what you think, if you're seeing size reductions in your bundles or if you have any questions about upgrading.
A few months ago, at Google I/O, we announced the beta of App Check, Firebase’s new mobile and web API security solution. App Check is an additional layer of security that protects access to your services by attesting that incoming traffic is coming from your app, and blocking traffic that doesn't have valid credentials. Right now, App Check is available for Cloud Storage, Realtime Database, and Cloud Functions for Firebase. In case you missed the launch, check out our introduction video to see how App Check works.
Today, we’re happy to announce three new features we’ve added to the App Check beta: support for App Attest on iOS, configurable time-to-live values (TTLs) for tokens, and support for protecting non-Firebase backends with App Check.
We know how important security is, so we wanted to make sure we’re integrated with the latest app attestation providers for our main platforms, and that includes iOS. To that end, we’ve added support on iOS for App Attest, Apple’s app attestation technology that was recently featured at Apple’s WWDC conference.
App Attest can be used to assert that a request comes from a legitimate instance of your app that satisfies three conditions:
You can use App Attest as an App Check provider on any iOS device that supports it. On devices without App Attest support, you can continue to use DeviceCheck with App Check. See our developer guide to learn how to use App Check with App Attest on iOS.
To give you the power to choose how you want to balance security and usability in your app, we’ve added the optional ability to set the TTL of App Check tokens. By using a short TTL, you optimize for increased security; on the other hand, a longer TTL can improve responsiveness and minimize quota usage. You can even customize the TTL individually per attestation provider. Configurable TTLs are supported when using App Attest, Device Check, SafetyNet, reCAPTCHA v3, and custom providers.
If you use your own backend services alongside Firebase, App Check can still help you! With our newest beta, you can protect your non-Firebase resources with App Check. This protection is currently possible on any backend service or service proxy that can run the Firebase Admin SDK for Node.js, including Cloud Run & GKE instances, and even bare metal servers.
If you already use App Check with RTDB, Storage or Functions, it is now really easy to extend App Check’s protections to your own server as well. It only takes a few lines of code on the client and on the backend to start protecting your resources today. Take a look at our guides for iOS, Android, and web to learn how.
This is part of a series of articles about app quality. Here is an overview of all the other articles:
Apps and games have evolved rapidly in recent years, and user expectations for high performance have increased right alongside them. Today’s users don’t just demand speed and performance — they reward it. A 2019 study found that retail sites saw 8% more conversions when they reduced their mobile site load times by one-tenth of a second. And travel sites boosted conversions by just over 10%.
As you reach more users across different devices, locations, OS versions, and networks, optimizing performance becomes even more of a moving target. To understand the unique context behind performance issues, you need actionable insights about your app performance from a user's perspective. With performance data that allows you to spend less time putting out fires, you can devote more time to creating delightful experiences knowing that no bug or glitch will slip through the cracks.
With performance data that allows you to spend less time putting out fires, you can devote more time to creating delightful experiences knowing that no bug or glitch will slip through the cracks.
In this article, we’ll explore some Firebase Performance Monitoring features that can help you keep an eye on your app’s performance and understand the experience from a user's point of view.
Real-time app performance metrics
Releasing a new feature that performs well for every user — no matter their location, device, or network speed — can be challenging if you don’t have the timely information you need to gauge performance across a range of variables. When poor performance and low app ratings occur, you need clear insights to deliver an experience worthy of a 5-star review.
Firebase Performance Monitoring processes your app performance data in real time so you can monitor new releases during development and post-launch. For instance, you can gather performance data from Firebase Emulators or virtual devices on Firebase Test Lab to test your app locally before launch. And after launch, you can get insights about metrics related to screen rendering and network requests to learn how your app is performing among different user segments.
By learning how your app responds for different groups of users, you can quickly take action to fix any errors and ensure users won’t delete your app to find one that works better on their device.
Performance Monitoring dashboard highlighting real-time metrics
Customizable Metrics Board
In the first blog post of this series, we highlighted some standard app performance metrics to keep top-of-mind, such as app start-up time, screen rendering performance, and network performance. However, sometimes the influx of real-time data after a big release can feel overwhelming, and identifying where you should focus and take action can be a daunting task.
With the revamped Performance Monitoring dashboard, you can customize your app performance metrics board to highlight the most important metrics for your app. For example, if you’re releasing updates on a shopping app, you can select and track slow-rendering frames on the checkout screens. This helps ensure your customers are enjoying a seamless experience from start to finish. You can also break down your key metrics by country, device, app versions, and OS level for a deeper dive into your performance data.
By learning how quickly your app responds for different groups of users, you can take action to fix latency issues and ensure users won’t delete your app to find one that works better on their device.
Additionally, Performance Monitoring allows you to implement custom code traces, which help monitor the performance of your app between two points in time. You can also create your own traces to capture performance data associated with specific code in your app. For example, you could use custom code traces to measure how long it takes your app to load a set of images and make sure the graphics aren’t causing too much lag.
Compare performance between app versions
Retaining a diverse user base isn’t easy without understanding how specific user segments are engaging with your app — especially when their experience isn’t up to par. To make sure every new release performs at its best once it reaches a large number of users, you can use the new Performance Monitoring dashboard to identify app performance changes that need immediate attention.
The metrics board enables metric performance tracking across versions. If your latest release calls a new API at start-up, you can track latencies in app start time between the latest version of your app and previous versions. The traces table is especially helpful to understand how your traces are trending across selected time ranges. That means you no longer have to wait for app store reviews or support tickets to know when your app performance is lagging.
Performance Monitoring traces table
Track trends, regressions, and severe issues
One of the most important ways to grow and engage your audience is by constantly releasing new features and updates to your app. But any code or configuration changes to your app or any of its many dependencies carry a risk of degrading your app’s performance or causing issues with user experience. For example, if your e-commerce app makes dozens of API calls to fetch your catalog and product details, users might experience frustrating lags during their shopping experience.
By tracking trends and regressions with Performance Monitoring, you can quickly act on the most critical issues and get ahead of low ratings on the app store.
Improve user retention with Performance Monitoring
GameNexa Studios, an India-based app developer, seized an opportunity to invest in improving its app quality when their ad sales were disrupted by COVID-19. By combining Firebase Performance Monitoring and Firebase Crashlytics, the team gained actionable insights about its user base and improved their most popular app’s experience across the board. And by reducing the number of performance issues affecting its users, GameNexa ended up boosting both user retention and session duration, and increased in-app purchases by 2.5X.
Stay ahead of app stability and performance issues
To deliver the fast, consistent experience app that users expect, you need a strategy backed by tools that help you act quickly and fix significant issues on the fly. With detailed, actionable data and insights from Firebase, app developers and product managers can make smarter decisions before launch, tackle urgent issues swiftly after releasing an update, and quickly and confidently roll out new features that keep users coming back.
To get started with Firebase Performance Monitoring, integrate the Performance Monitoring SDK into your app and identify the metrics that matter most to your app’s success.
If you’ve used Firebase Remote Config, then you know how it can help you control and optimize your app on the fly. And recent improvements help you better visualize your configuration so you can more easily update your app to drive the outcomes you want, like increasing subscription sign-ups. But what if you have a portfolio of many apps that you want to optimize at the same time?
That was the goal of CrazyLabs, a hypercasual and casual publisher whose games, including Super Stylist - Makeover & Style Fashion Guru, Tie Dye, and Phone Case have been downloaded more than 4 billion times. Their business model relies on identifying potentially high-profit games early on among many applicants and helping them scale. CrazyLabs needed a solution that could help them test up to 30 configurations per title across up to 15 titles at a time in order to increase revenue without decreasing user engagement.
Learn how CrazyLabs used Remote Config and AdMob to optimize monetization at scale for all of their titles in our new case study.
Get ready for a new faster web experience with Firebase. For the longest time you all have asked us to optimize the size of our JavaScript libraries, and we've done just that. We've released brand new beta libraries that are significantly smaller in size. Some are even up to 80% smaller! We've managed to reduce the size all without removing any features from the previous version too!
How did we do this? We did it by embracing the modern web and its new features. We converted our library to take advantage of code elimination features of modern day JavaScript tools like webpack and Rollup. While this change drops size, it did require us to change the library's API for these tools to identify which functionality isn't being used in your app.
This is a breaking change that will require you to update your code. We have released the library to npm under the beta tag, but we will be publishing it to the main tag in the near future. You can get started today and use the compatibility library to make the upgrade easier.
beta
The new API
Take a look at our new beta API. The first thing you might notice is that we've removed all side-effect imports. You know, the import firebase/<service> lines of code, which aren't very clear about what exactly is being imported. Or as webpack succinctly puts it "A "side effect" is defined as code that performs a special behavior when imported, other than exposing one or more exports. An example of this are polyfills, which affect the global scope and usually do not provide an export."
import firebase/<service>
From now on in our API, we explicitly export each function from the package, which makes it clear what you are consuming in your app.
import { initializeApp } from 'firebase/app'; import { getAuth, onAuthStateChanged } from 'firebase/auth'; const firebaseApp = initializeApp({ /* config */ }); const auth = getAuth(); onAuthStateChanged(auth, user => { console.log(user); });
This new API has a lot of familiar functions from the previous one. The main difference is the organization of the code. We've removed all side-effect imports and created individual entry points for each and every package.
import { initializeApp } from 'firebase/app'; import { getFirestore, collection, getDocs } from 'firebase/firestore'; import { getAuth, onAuthStateChanged } from 'firebase/auth'; import { getStorage, uploadBytes } from 'firebase/storage'; import { getRemoteConfig, fetchAndActivate } from 'firebase/remote-config'; import { getRemoteConfig, fetchAndActivate } from 'firebase/remote-config'; import { getDatabase, ref, get } from 'firebase/database'; import { getMessaging, getToken } from 'firebase/messaging'; import { getPerformance } from 'firebase/performance'; import { getAnalytics, logEvent } from 'firebase/analytics';
Modern JavaScript tools like webpack and Rollup discourage the use of side-effect imports. This is because side-effect imports are like an unknown quantity. Module bundlers don't know what they are going to bring in. Having explicit functions to import gives these tools a better understanding of how to build your code.
import { initializeApp } from 'firebase/app'; import { getAuth, onAuthStateChanged, getRedirectResult } from 'firebase/auth'; const firebaseApp = initializeApp({ /* config */ }); const auth = getAuth(); onAuthStateChanged(auth, user => { console.log(user); });
When these tools can understand your code they can eliminate unused parts of a codebase, this is a feature called tree-shaking. This is what makes the new library so much smaller. By reorganizing the Firebase library we can take advantage of tree-shaking and remove all unneeded parts of the library from your app. This size savings we have seen have been significant.
How much smaller is the new library?
Let's get real for a second and talk library size. Firebase has always been a larger library on the web. The web itself is a balance of features and performance and we wanted to make that balance a lot easier for you. This was the biggest reason for us to take on tree shaking. We suspect that no matter what you'll see a sizable drop in your bundle size. However, we're hoping that with specific use cases you'll see significant size reductions.
The two biggest areas of improvement we have seen in our early studies are with the new firebase/firestore/lite package (more on that in a bit!) and firebase/auth. The table below shows a package, the current SDK version size, the current beta version size for a basic use case, and the percentage the SDK is lighter than the current v8 version.
firebase/firestore/lite
You'll notice that authentication can be up to 72% lighter than before. When using firebase/firestore/lite you can save 84% from the current version of firebase/firestore if you only need one-time reads. This new "import only what you need" method allows you to decide what to include in your bundles and make that features and performance balance much easier.
firebase/firestore
Introducing Firestore Lite
Firestore is such a powerful library because it does so many things behind the scenes that we encourage you all to take for granted. Firestore has a complex caching, realtime streaming, persistent storage, multi-tab offline sync, retries, optimistic concurrency, and so much more. But we heard from you all that sometimes you just need to get a collection, and don't want to include all of Firestore's other features in your site. For those cases we wanted to make Firestore a simple and light solution, so we created a brand new subpackage: firebase/firestore/lite.
import { initializeApp } from 'firebase/app'; import { getFirestore, collection, getDocs } from 'firebase/firestore/lite'; const firebaseApp = initializeApp({ /* config */ }); const db = getFirestore(); const snapshot = await getDocs(collection('cities'));
This sample uses firebase/app and the new firebase/firestore/lite package. You'll notice that calling getDocs() initiates a one-time data read. Firestore Lite allows you to create, read, update, and delete data with Firestore in a much smaller library. Realtime streaming is not included, but you can always switch back to firebase/firestore if that's what you need. If you want to adopt an advanced loading strategy, you can even load firestore/lite for a fast initial page load and lazy load firebase/firestore for progressive enhancement.
getDocs()
firestore/lite
Firestore Lite is significantly smaller. As you saw in the table above, it can be 84% lighter. We know that you Firestore users will find a great fit for this library.
An easier upgrade with our compatibility library
Change is never easy. Our new library provides new benefits but it's hard to go back and rewrite code that already works. To make that process easier, we're also releasing a compatibility library that allows you to port your code piece by piece.
import { initializeApp } from 'firebase/compat/app'; import 'firebase/auth/compat'; import { onAuthStateChanged } from 'firebase/auth'; const firebaseApp = firebase.initializeApp({ /* config */ }); const auth = firebaseApp.auth(); onAuthStateChanged(auth, user => { console.log(user); });
The new modular SDK cannot be used alongside the existing namespace based library, but the compatibility library allows you to use both APIs at the same time. You won't get all of the tree shaking advantages upfront, but once you match the new modular library you can switch off the compatibility library and rake in the savings.
What about framework integrations?
Libraries like AngularFire, ReactFire, and RxFire will be compatible with the new SDK in the near future. We are close to completing them, so hang tight! Track their progress in these issues on GitHub.
Once they have been updated there will be little to no work to move over, as we will update them underneath the hood. However, AngularFire will not initially receive all of the tree shaking benefits since it follows a classical OOP structure. We plan on releasing an API proposal in the near future to optimize for tree shaking and provide an easy path to upgrade as well.
Get started today
The new JavaScript library is available today on npm under the beta tag. We want to hear your feedback! Tell us what you think of the new API and any size savings you've seen. We're excited about the future of this library and the performance benefits it brings.
To learn more about the new library, see our talk at Google I/O. Also check out our upgrade guide for in-depth information about the upgrade process.