Instantly share code, notes, and snippets.

Embed
What would you like to do?
A simple guide that explains how to deploy PWA Starter Kit to Firebase

Note: this guide explains step by step how to add Firebase to PWA Starter Kit.

If you already have some Firebase knowledge and you just want to get everything ready out of the box, you might want to checkout the Firebase branch on the PWA Starter Kit repo, that already contains all the needed files.

Deploying prpl-server to Firebase

Firebase Hosting alone is not sufficient for hosting the prpl-server build since it requires some server-side processing of the user agent string. Instead, you will have to use Firebase Functions for that.

  1. Sign up for a Firebase account

  2. Head over the Firebase Console and create your project. Make note of the project ID associated with your app

  3. Install Firebase Command Line Tools:

    $ npm i -g firebase-tools
  4. Log into your Firebase account from the CLI by running:

    $ firebase login
  5. Set the default Firebase project for your PWA. You can do that by either running this command:

    $ firebase use --add <project-id>

    Or by creating a file named .firebaserc in the root of your application, containing:

    {
      "projects": {
        "default": "<project-id>"
      }
    }
  6. Create a file named firebase.json in the root of you PWA and paste this JSON in it:

    {
      "hosting": {
        "public": "build",
        "ignore": [
          "firebase.json",
          "**/.*"
        ],
        "rewrites": [
          {
            "source": "**",
            "function": "app"
          }
        ],
        "headers": [
          {
            "source" : "**/service-worker.js",
            "headers" : [
              {
                "key" : "Service-Worker-Allowed",
                "value" : "/"
              },
              {
                "key" : "Cache-Control",
                "value" : "no-cache"
              }
            ]
          }
        ]
      }
    }

    The public field tells Firebase that the build folder should be statically served through its CDN. The rewrites field tells Firebase to send each request to the app function - we will create it later. The headers field tells Firebase to add the Service-Worker-Allowed header when the user requests a service-worker.js file

  7. Create a functions folder. This folder will contain the code executed by Firebase Functions. You will need to create two files in it:

    1. package.json
      {
        "name": "functions",
        "description": "Cloud Functions for Firebase",
        "scripts": {
          "serve": "firebase serve --only functions",
          "shell": "firebase functions:shell",
          "start": "npm run shell",
          "deploy": "firebase deploy --only functions",
          "logs": "firebase functions:log"
        },
        "dependencies": {
          "express": "^4.16.4",
          "firebase-functions": "^2.1.0",
          "prpl-server": "^1.3.0",
          "rendertron-middleware": "^0.1.3"
        },
        "private": true
      }
    2. index.js
      const functions = require('firebase-functions');
      const prpl = require('prpl-server');
      const express = require('express');
      const rendertron = require('rendertron-middleware');
      
      const app = express();
      
      const rendertronMiddleware = rendertron.makeMiddleware({
        proxyUrl: 'https://render-tron.appspot.com/render',
        injectShadyDom: true,
      });
      
      app.use((req, res, next) => {
        req.headers['host'] = '<YOUR HOST URL HERE>';
        return rendertronMiddleware(req, res, next);
      });
      
      app.get('/*', prpl.makeHandler('./build', require('./build/polymer.json')));
      
      exports.app = functions.https.onRequest(app);
      This is the main file for our Firebase Functions. First, we create an Express app. Then, we add a rendertron middleware, that will server side render our PWA for crawler bots, and we give to PRPL Server the responsability to answer to any other GET request. Last but not least, we tell Firebase to make our Express app handle each request to the app function.
  8. Install the npm dependencies inside the functions directory:

    $ cd functions && npm i
  9. Now that the Firebase Functions part is ready, we have to setup our build to make it work correctly with it. Since Firebase Functions cannot require files that are not inside the functions directory, we need to build the PWA and make Gulp move the files needed by PRPL Server to the functions directory, while leaving the static assets in the build directory. To do that, we are going to add a new task to gulpfile.js:

    /**
     * Builds the Firebase-ready version of the PWA, moving the necessary
     * files to the functions folder to be used by PRPL Server
     */
    gulp.task('firebase', () => {
      // These are the files needed by PRPL Server, that are going to be moved to the functions folder
      const filesToMove = [ 'build/polymer.json', 'build/**/index.html', 'build/**/push-manifest.json' ];
      // Delete the build folder inside the functions folder
      return del('functions/build')
        .then(() =>
          // Copy the files needed by PRPL Server
          new Promise((resolve) =>
            gulp
              .src(filesToMove, { base: '.' })
              .pipe(gulp.dest('functions'))
              .on('end', resolve)))
        // Delete them from the original build
        .then(() => del(filesToMove));
    });
  10. Optional: If you also need to use Firebase Auth, you have to make sure to tell the Service Worker to ignore its reserved namespace. Add the following code to your sw-precache-config.js to tell your Service Worker to use index.html as a fallback for everything but Firebase reserved namespace:

    module.exports = {
      ...
      navigateFallback: '/index.html',
      navigateFallbackWhitelist: [ /^\/[^\_]+\/?/ ]
    };
  11. Add a script named build:firebase in your project root's package.json:

    "build:firebase": "polymer build --auto-base-path && gulp firebase"
    
  12. Now that you have everything set up, just run the build:firebase script to build the PWA for Firebase:

    $ npm run build:firebase
  13. Finally, deploy your PWA to Firebase:

    $ firebase deploy --only functions,hosting
@itsDineth

This comment has been minimized.

Copy link

itsDineth commented Jun 16, 2018

Hi, the URL passed to Rendertron seems to be of the function's URL though (*. cloudfunctions.net)—as opposed to the request URL (hosting)

@diddledan

This comment has been minimized.

Copy link

diddledan commented Jun 18, 2018

the service worker needs to be uncached. By default firebase hosting adds a cache-control: max-age=3600 header, so you need to add an extra element in the headers object for **/service-worker.js to ensure that it isn't cached:

"headers": [{
    "source": "**/service-worker.js",
    "headers": [
        {
            "key": "Service-Worker-Allowed",
            "value": "/"
        },{
            "key": "Cache-Control",
            "value": "no-cache"
        }
    ]
}]
@frankiefu

This comment has been minimized.

Copy link

frankiefu commented Jun 22, 2018

prpl-server looks for push-manifest.json in the build folder. So you will need to copy push-manifest.json to functions/build/**/ or it won't generate the link headers for push. Something like this:

/**
 * Builds the Firebase-ready version of the PWA, moving the necessary
 * files to the functions folder to be used by PRPL Server
 */
gulp.task('firebase', () => {
  // These are the files needed by PRPL Server, that are going to be moved to the functions folder
  const filesToMove = [ 'build/polymer.json', 'build/**/index.html', 'build/**/push-manifest.json' ];
  // Delete the build folder inside the functions folder
  return del('functions/build')
    .then(() =>
      // Copy the files needed by PRPL Server
      new Promise((resolve) =>
        gulp
          .src(filesToMove, { base: '.' })
          .pipe(gulp.dest('functions'))
          .on('end', resolve)))
    // Delete them from the original build
    .then(() => del(filesToMove));
});
@keanulee

This comment has been minimized.

Copy link

keanulee commented Jun 26, 2018

rendertron.makeMiddleware() doesn't work because the req argument that Firebase Functions gives is based on a different URL (e.g. https://us-central1-pwa-starter-kit.cloudfunctions.net/app/). This URL cannot be rendered by Rendertron/browsers because static assets are not available at this host. I modified the handler in index.js to make Rendertron work (demo https://pwa-starter-kit-4225c.firebaseapp.com/):

const rendertronMiddleware = rendertron.makeMiddleware({
  proxyUrl: 'https://render-tron.appspot.com/render',
  injectShadyDom: true,
});

app.use((req, res, next) => {
  req.headers['host'] = 'pwa-starter-kit-4225c.firebaseapp.com';
  return rendertronMiddleware(req, res, next);
});
@PiusWilli

This comment has been minimized.

Copy link

PiusWilli commented Jun 30, 2018

Unfortunately i stuck on implementing firebase Auth on my test pwa.
The auth Popup is blank and i get an Error in the console
{code: "auth/network-request-failed", message: "A network error (such as timeout, interrupted connection or unreachable host) has occurred."}

login(){
    var provider = new firebase.auth.GoogleAuthProvider();
    var auth = firebase.auth();
    auth.signInWithPopup(provider)
        .then((result) => console.log('Signin result', result))
        .catch((error) => console.error('Sigin error', error));

I think it has something to do with the Reserved URL __/auth/.. which is disturbed by the service worker
Reserved URLs
Investigation the Network traffic, i see the following URL is responded by the service worker instead of firebase auth.
https://mytestproject.firebaseapp.com/__/auth/iframe?apiKey=AIzaSyDcQ0xyz......................

Does someone know how to ignor "/__/auth/*" urls by the service worker. Or am i on the wrong track, and the service worker has nothing to do with the issue?

@Dabolus

This comment has been minimized.

Copy link
Owner Author

Dabolus commented Jul 11, 2018

Thanks to everyone, I added your solutions to the gist.
@keanulee I also added a note that points to your firebase branch on the Starter Kit.
@PiusWilli take a look at the new optional step 10, it should solve your issue.

@PiusWilli

This comment has been minimized.

Copy link

PiusWilli commented Jul 23, 2018

@Dabolus thx a lot!

@HeratPatel

This comment has been minimized.

Copy link

HeratPatel commented Nov 26, 2018

@PiusWilli
Thanks for the wonderful gist,
I have a question.
Do we need 'firebase-admin' dependency in functions folder ?

@Dabolus

This comment has been minimized.

Copy link
Owner Author

Dabolus commented Dec 7, 2018

@HeratPatel yeah actually firebase-admin is there when you generate your functions project with firebase init, but it shouldn't be needed. I think it's safe to remove it.

@lucasfrib

This comment has been minimized.

Copy link

lucasfrib commented Dec 27, 2018

After optional alteration in 10 (sw-precache-config.js lines), the chrome lighthouse audit (progressive web app) drop 100 to 69.
Why this happens?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment