Search code examples
node.jsfirebaseexpressgoogle-cloud-functionsfirebase-hosting

Hosting static content in public directory on firebase


I am hosting an app on Firebase making use of cloud functions and hosting. The former serves dynamic content in the form of a NodeJS express app with an API included, the latter serves static content such as css, javascript, etc. for my express app.

(Here is a good tutorial to get setup with express & firebase hosting)

Usually in express apps with the standard structure setup, one has a few folders created such as bin, routes, views, and public. Most of these are easily replicated in your firebase app.

Problem:

The issue is working with hosting & functions simultaneously.

Using the following index.js (in express known as app.js), I can successfully view & emulate my application locally using firebase emulators (functions, hosting, etc.)

const functions = require("firebase-functions");
const admin = require("firebase-admin");
const express = require("express");
const engines = require("consolidate");
...

admin.initializeApp({
    credential: admin.credential.applicationDefault(),
    databaseURL: "https://....firebaseio.com"
});

// app routes
const usersRouter = require('./app/users');
...

const app = express();

// #Working locally only - for deplopyment, all static linkes must load resources as [hosting-domain.app.com]/css/styles.css
app.use("/public", express.static(path.join(__dirname,'../public')));

app.use(flash());

app.engine('pug', require('pug').__express)
app.set('views', __dirname + '/views');
app.set('view engine', 'pug');

// Use the createSession endpoint for login & verify each session
app.use(authenticate);

...
app.use('/user', usersRouter);
app.get('/', (req, res) => {
    return res.redirect("/user/login");
});

app.get('*', (req, res, next) => {
    res.render("404");
})

exports.app = functions.https.onRequest(app);

// api routes
exports.api = require('./api/api.js');

How can I serve static content successfully on firebase hosting while using firebase functions for dynamic content?

(answer follows)


Solution

  • scroll to bottom for TL;DR & solution

    While writing the question - I found the solution after thinking about each of the firebase features and what they do individually.

    Hosting static content locally vs deployed on firebase

    When deploying, the path for assets e.g. [my-domain.web.com]/public/styles/my-styles.css which I can get locally doesn't load when deployed. Instead, I need to use [my-domain.web.com]/styles/my-styles.css.

    Works Locally, but not when deployed:

    [my-domain.web.com or localhost:5000]/public/styles/my-styles.css
    

    Works Locally AND when deployed

    [my-domain.web.com or localhost:5000]/styles/my-styles.css
    

    The reason for this is anything inside of the public directory is hosted directly to your domain. Thus, using the express.static requires the same folder structure, etc. however the contents of the public directory are hosted (automatically) from the root of your domain.

    In express, one needs to specify where your static content can be found which is then hosted by nodejs or other hosting feature of your choice.

    BUT Firebase does the static content hosting for you automatically. Thus, there is no need to include or even have the following line:

    app.use("/public", express.static(path.join(__dirname,'../public')));
    

    as it only serves as a point of confusion (and also serves files locally only i.e. the point of confusion). Keep in mind, anything inside of the public folder takes precedence over dynamic content.

    See the little blue note right above the middleware section:

    Note: The static files in the public directory take precedence over the rewrites, so any static files will be served alongside the Cloud Functions endpoints.
    

    The public directory being:

    my-firebase-app
        \ functions
        \ public               < ------ this one
        \ ..other files
    

    TL;DR

    Given the folder structure:

    my-firebase-app
        \ functions /
            \ views /
                \ root.pub     < ---- pug template file
        \ public \             < ------ this one
            \ styles /
                \ my-styles.css < ----- styles file
        \ ..other files
    

    Ensure all your pug/handlebars/ejs, etc templates assume the public directory is the root of your domain, thus your static content is loaded as [localhost:5000 or yourdomain.web.app]/styles/my-styles.css

    Normal Express App Normally hosting your express app will use something like:

    app.use("/public", express.static(path.join(__dirname,'../public')));
    

    where in your pub (template file) you will have something like

    link(rel="stylesheet", href="/public/styles/my-styles.css")
    

    Firebase Functions + Hosting App, this should be changed to:

    • Remove the app.use("/public"...) function (firebase hosting hosts this to your root domain)

    • any links to static content should use

      link(rel="stylesheet", href="/styles/my-styles.css")

    Note the omission of the "/public" in the href