This article is somewhat of a memoir where I share my experience getting dynamic routing to work with Firebase. To understand what’s going on, you should be familiar with their Hosting and Functions products. I’ve condensed all the essential technical stuff into the mini tutorial. Feel free to skip this section if you’re willing to read the whole article.
Firebase Hosting serves static files from your custom domain. Firebase Functions are executed by calling them via ugly cloudfunctions.net
URLs. As of Google IO 2017, they now play nicely together. In your firebase.json
’s hosting
configuration, you can rewrite
any specific route on your custom domain to call a function
instead of serve a static page. Check out the docs.
To simulate dynamic routes like example.com/posts/123
, I did the following: Create a function called [post](https://gist.github.com/pejalo/1715c896658d660e0ded0d49d9910896)
. Set up firebase.json
to have all requests for /posts/**
call the post
function. The function fetches data and returns pre-rendered HTML containing all the proper social and SEO meta tags for the requested post, to make crawlers happy and support unfurling.
The page also includes a JavaScript window.location
redirect snippet, so a real user is redirected to example.com/?post=123
. This static index page reads the query parameter, displays the correct screen with the post content, and leverages HTML5’s replaceState()
to put example.com/posts/123
back into the browser’s address bar. Serverless dynamic routing for all!
Firebase has single-handedly reignited my relationship with web development. Instead of building a frontend and backend for every project, I now only ever have to worry about the frontend. As broad as Firebase’s offering is, it still lacks many sought after features (like search, complex database querying, insight into usage, etc). But I’m so invested at this point that I’d rather jump through a couple hoops to make something work with Firebase than go back to setting up a foundational backend myself as if we were still living in a pre-Parse era. (Yes, us developers are lazy.)
A couple weeks ago I was working on a Product Hunt clone that had a pretty simple requirement: dynamic routing. Each post needed its own URL to share around the web, like example.com/posts/123
. These links had to return pre-rendered social and SEO meta tags to support unfurling.
Up until this point I had everything happily hosted on Firebase as a notorious single page app, connecting directly to the database from the client. But with this new pre-rendering requirement, which was not yet supported by Firebase, it looked as though I’d have to point our domain to a custom VPS and host the site myself.
So I spent a weekend learning about Dokku (a great option BTW), getting our site up and running, doing some load testing, watching the site crash, scouring the internet to try and figure out why, convincing myself the app had memory leaks, then realizing I just needed a machine with more memory, bla bla bla all the while wishing I could just get back to building features and stop dealing with this dev ops nonsense.
A couple days later, someone in the Firebase Slack group posted a Google IO livestream. I tuned in just for fun and right there, the exact feature was announced that I needed: Hosting + Functions! With this new integration developers can run some backend code behind a custom domain before sending HTTP responses to the client. Within a couple hours I had dynamic routing working in production and could put Dokku back on the shelf.
Firebase Hosting now has a new configuration option. In firebase.json
, where you specify all your hosting behavior like caching, rewrites, redirects, etc, you can rewrite
any path to point to a function instead of a static file. Here’s an example:
// firebase.json
{ "hosting": { "public": "public", "rewrites": [ { "source": "/posts/**", "function": "post" } ] }}
With this config, any requests to example.com/posts/postidorhashorwhatever
will ignore whatever static files you might have hosted there and call your post
function instead. The function is called with Express Request and Response objects through which you can send an arbitrary HTTP response.
This lets us perform any logic we want before returning to the client! (As long as it runs within Firebase’s cloud function Node environment…) Great, you might think: We can return anything, but how do we return an entire page of our website showing the proper post content, when the website is built to be served as static pages?
The truth is, I don’t know. Maybe some day the Webpack gurus will create super fancy plugins that separate dynamic routes into functions with their own deployment configurations. Who knows what they’ll come up with next. Meanwhile, I’ve got a hacky solution that isn’t too complex and achieves the same result.
Because our function can return anything, it’s pretty easy to have it return valid HTML with whatever content-specific social and SEO meta tags we need. This is the only part of our page that really needs to be pre-rendered to support unfurling. And if we can make it return an HTML string, we can certainly include a snippet of JavaScript in there to help get the rest of the page displayed asynchronously.
Here’s a shortened version of what my post
function looks like:
As you can see, the HTML response to be sent includes '<script>window.location="[https://example.com/?post=](https://example.com/?post=)' + post.id + '";</script>'
. A crawler just looking for your meta tags will be happy to receive them and won’t bother to execute this script. But a real user who loads this page in a browser will be immediately redirected to example.com/?post=123
.
Now what exactly happens on example.com/?post=123
is out of the scope of this article, because it completely depends on the frontend framework you’re using. You just want to show the post content without redirecting the browser back to /posts/123
. (My website was built with Vue.js, so in my index page’s beforeMount()
function I check this.$route.query.post
. If it exists, I show the requested post within the index page, without touching the route.) The important thing to know is that you can manipulate what’s displayed in the browser’s address bar to look nice again. To replace the ugly ?post=123
with /posts/123
, you can call window.history.replaceState({}, ‘Title’, ‘/posts/’ + postId);
.
Voila! Unless the user paid attention compulsively to their address bar, all they know is that they clicked on a link and ended up on the proper page of our website.
Honestly, as a design freak, this double-redirection does irk me a bit. It’s not sparkling clean. But it only ever happens to users who navigate to our site via a standalone link like example.com/posts/123
from another source. The jarring experience of jumping from the referring website to our site overshadows the quick redirects happening in the background.
And you know what’s dirtier than a little redirect hack? Spinning up a new virtual machine, installing Ubuntu, adding your SSH keys, creating a new sudo user, sudo apt-get update
, etc etc you get the picture…
Firebase FTW.
Made it to the end and learned something? Leave some love 💚
Peter LoBue is a freelance UX designer, web developer and iOS developer living between Philadelphia and San Francisco. Reach out for availability: @pejalo