Firebase is a great tool for getting data from a server to a client. Firebase handles caching, retries, socket management, and all the other unpleasant details of getting data to a client with spotty internet connection. In particular, Firebase is excellent for mobile web and mobile apps. In this article, I'll walk you through using Firebase with Preact, a lightweight React alternative, to build a simple app with server-side rendering.

Setting Up The App

Here's the pushups app on GitHub. The general idea is that you assign pushups to people when they're late for meetings and then mark your pushup assignments as complete. Here's a quick video:

To set up a Firebase project for this app, follow Firebase onboarding and create a new project. Clone the GitHub repo and copy the project's web config (from Authentication -> Web Setup) into firebaseConfig.js. Your firebaseConfig.js should then look like:

module.exports = {
  apiKey: /* ... */,
  authDomain: /* ... */,
  databaseURL: /* ... */,
  projectId: /* ... */,
  storageBucket: /* ... */,
  messagingSenderId: /* ... */

You should then be able to run npm start and run the app.

How the App Works

There's 3 meaningful files in this project:

The firebase.js file is the simplest, it configures firebase and exposes a firebase database handle:

The client-side app in client.jsx is a single Preact component called App. Once transpiled and browserify-ied, the typeof window logic will let the app run when included with a <script> tag. The lastChild bit tells Preact to replace the server-side rendered App component that the server will insert into the body. I got this pattern from this JSFiddle.

There's two data models that this app cares about. The people property tracks the total number of pushups each person owes, and the timeline property tracks each individual pushup assignment so the user can complete them. When the component mounts, the component starts listening to Firebase for changes:

There's one more subtlety with the App component. In Preact, the render() function takes 2 parameters, the component props and the state. In the browser, the App component will track its internal state using state and setState(), much like React sans Redux. However, the server-side rendering tool I used for this project passes state via props, so render() should handle both.

Server-Side Rendering

Great, so now that we've built a rudimentary client-side app that listens to Firebase. How do you get the server to render the app? The preact-render-to-string npm module can render Preact markup to a string:

The first parameter to preact-render-to-string's render() function is a Preact virtual DOM element (e.g. preact.h) and the second parameter is props to pass to that element. These props will get passed to the App component's render() function as the first parameter. Once you've rendered the component to HTML, all you need to add some surrounding HTML boilerplate:

The last step is getting the actual data from Firebase, so we can render real data on the server. Unfortunately, I haven't been able to figure out how to get the Firebase web client working in Node.js. There's the firebase-admin module, but that module comes with its own complexities, so in this case I found the easiest solution to be Firebase's REST API. Two simple HTTP requests using superagent and the app has all the data it needs for initial render.

And that's a wrap! Server-side rendering sounds intimidating, but all you need to do is load the correct data and render the right HTML. With Preact, preact-render-to-string, and the lastChild trick rendering the HTML is straightforward. Loading the correct data is the hard part, but if you structure your firebase data correctly you can load the data with one or more calls to Firebase's REST API (or using the realtime connection, if you can figure out how to make that work in Node.js).

Got JavaScript fatigue? Your diet might be every bit as responsible as Firebase's lackluster documentation. I just bought Dave Asprey the Bulletproof Coffee Guy's new book "Head Strong" to learn more about how I can optimize my diet and environment for maximum productivity.

Found a typo or error? Open up a pull request! This post is available as markdown on Github
comments powered by Disqus