The example repository is located at https://github.com/1919yuan/nuxt-firebase-example
I’ve been volunteering in an NPO and learning webapp development recently. After procrastinating for a long time, I finally settled down on using Nuxt.js for developing the website for the NPO. Even though there are many resources on nuxtjs.org about deploying to different environments, there is not an official guide about deploying an universal mode Nuxt.js application to Firebase. This becomes more difficult when you want to leverage Firebase’s authentication and firestore service in universal mode. After a series of trial and error, and with the help of many valuable articles and repositories 123456, I was able to finally configure the app with authentication and firestore on Firebase hosting and Firebase functions. It was an interesting learning process, so I would like to share the steps to recreate a minimal working Nuxt.js app with Firebase authentication, firestore deployed to Firebase Hosting and Functions in universal mode.
Project setup
TL;DR: Create Nuxt.js repo and Firebase project.
Create a Nuxt.js project
Install nvm
and use Node.js 10 runtime if you haven’t set
that up already. Then create a Nuxt.js app.
npx create-nuxt-app nuxt-firebase-example
You are free to choose any options for the questions asked by the npx CLI, but below are the choices I used in the example Github repository. Be sure to choose SSR mode for your app because that’s the most difficult part that this post is about.
? Choose programming language JavaScript
? Choose the package manager Yarn
? Choose UI framework Buefy
? Choose custom server framework Express
? Choose Nuxt.js modules Axios, DotEnv
? Choose linting tools ESLint
? Choose test framework None
? Choose rendering mode Universal (SSR)
Note that after creation, nuxt-create-app
adopts the default folder structure
like the following:
$ ls nuxt-firebase-example
README.md assets components layouts middleware node_modules nuxt.config.js
package.json pages plugins server static store yarn.lock
However, because we want to deploy server side to Firebase functions, and client
side to Firebase hosting, we need to create two packages within our project
folder. Therefore, following the example in 1, we want to move the source
folders down to the src/
folder, with a structure like this:
$ ls nuxt-firebase-example
README.md src node_modules nuxt.config.js package.json yarn.lock
$ ls nuxt-firebase-example/src
assets components layouts middleware pages plugins server static store
There will be a functions/
directory in parallel with the src/
folder, for
deploying the built universal mode app after the next step.
Initialize the Firebase project
Then install Firebase CLI:
yarn global add firebase-tools
firebase login
# Follow the browser prompts to finish signing in your Firebase account for CLI.
firebase init
Choose “Hosting” and functions on the prompt like this:
? Which Firebase CLI features do you want to set up for this folder? Press Space
to select features, then Enter to confirm your choices.
◯ Database: Deploy Firebase Realtime Database Rules
◯ Firestore: Deploy rules and create indexes for Firestore
◉ Functions: Configure and deploy Cloud Functions
◉ Hosting: Configure and deploy Firebase Hosting sites
◯ Storage: Deploy Cloud Storage security rules
◯ Emulators: Set up local emulators for Firebase features
Create a new project using the Firebase CLI. You can do this at https://console.firebase.google.com too. Below are the options I used for the example repository.
? Please select an option: Create a new project
i If you want to create a project in a Google Cloud organization or folder,
please use "firebase projects:create" instead, and return to this command when
you've created the project.
? Please specify a unique project id (warning: cannot be modified afterward)
[6-30 characters]:
# This project id needs to be globally unique.
() nuxt-firebase-example-1919yuan
? What language would you like to use to write Cloud Functions? JavaScript
? Do you want to use ESLint to catch probable bugs and enforce style? No
? Do you want to install dependencies with npm now? Yes
? What do you want to use as your public directory? public
? Configure as a single-page app (rewrite all urls to /index.html)? No
You would also want to create a Firebase app inside your newly created firebase project. To do this, you can either create an app at console.firebase.google.com, or use the Firebase CLI like:
$ firebase apps:create web
Create your WEB app in project nuxt-firebase-example-1919yuan:
? What would you like to call your app? nuxt
✔ Creating your Web app
🎉🎉🎉 Your Firebase WEB App is ready! 🎉🎉🎉
App information:
- App ID: {your_app_id}
- Display name: nuxt
After the above commands, you’ll have a root project directory like:
$ ls nuxt-firebase-example
README.md firebase.json functions node_modules nuxt.config.js package.json
public src yarn.lock
$ ls nuxt-firebase-example/functions
index.js node_modules package.json
Install Nuxt.js modules
Now we will install related Firebase modules in addition to the initial Nuxt.js project setup.
# For the convenience of setting up Firebase client and using $fireStore
# objects across app and store.
$ yarn add @nuxtjs/firebase firebase
# Firebase Admin SDK and jwt-decode, for server side token verification.
$ yarn add firebase-admin jwt-decode
# Add corejs which is compatible with @nuxtjs/firebase
$ yarn add core-js@2.6.10
# Add cookie handling packages
$ yarn add cookie cookie-universal-nuxt
Make code and configuration changes
Setup Nuxt Firebase Module
Following the instructions by lupas@, we setup the module config for
@nuxtjs/firebase
:
...
modules {
...
'@nuxtjs/firebase'
...
},
firebase: {
config: {
apiKey: process.env.FIREBASE_API_KEY,
authDomain: process.env.FIREBASE_AUTH_DOMAIN,
databaseURL: process.env.FIREBASE_DATABASE_URL,
projectId: process.env.FIREBASE_PROJECT_ID,
storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.FIREBASE_APP_ID,
measurementId: process.env.FIREBASE_MEASUREMENT_ID,
},
services: {
auth: true,
firestore: true,
}
},
build: {
...
transpile: [
...
'/^nuxt-fire/',
...
]
...
},
srcDir: 'src'
Please note here that I couldn’t really get @nuxtjs/firebase
to work in
universal mode by following lupas@’s guide on 2, so I am only using
@nuxtjs/firebase
as a convenient wrapper for the Firebase client library. I
was only able to make Firebase authentication work in universal mode by
following Austin’s tutorial 1. Finally we also want to modify the
nuxt.config.js
file located at the root of the project directory to know that
the source files of our Nuxt.js project is located at the src
dir.
After the above setup, you may have noticed that we have already relied on various environment variables so far:
GOOGLE_APPLICATION_CREDENTIALS
FIREBASE_API_KEY
FIREBASE_AUTH_DOMAIN
FIREBASE_DATABASE_URL
FIREBASE_PROJECT_ID
FIREBASE_STORAGE_BUCKET
FIREBASE_MESSAGING_SENDER_ID
FIREBASE_APP_ID
FIREBASE_MEASUREMENT_ID
We will set these up using our dotenv
dependency chosen at the time we ran
create-nuxt-app
.
First, add dotenv initialization to nuxt.config.js
at the top:
require('dotenv').config()
Second, create the .env
file at the project root:
FIREBASE_API_KEY={your_api_key}
FIREBASE_AUTH_DOMAIN={your_project_id}.firebaseapp.com
FIREBASE_DATABASE_URL=https://{your_project_id}.firebaseio.com
FIREBASE_PROJECT_ID={your_project_id}
FIREBASE_STORAGE_BUCKET={your_project_id}.appspot.com
FIREBASE_MESSAGING_SENDER_ID={your_sender_id}
FIREBASE_APP_ID={your_app_id}
FIREBASE_MESUREMENT_ID={your_measurement_id}
GOOGLE_APPLICATION_CREDENTIALS="firebase_key.json"
The firebase_key.json
file is not needed for Firebase client initialization,
but Firebase Admin initialization on server side. You can download this service
account key file from “console.firebase.google.com > Project Settings > Service
Accounts > Generate new private key”. Save it to the root of your project dir
with the name firebase_key.json
.
Firebase Authentication in Universal Mode
Now we are going to follow the Austin’s article 1 to setup a Axios interceptor on client side and a Firebase admin token parser on server side.
There are many steps and Austin did a good job of explaining it. Please follow the tutorial there. The files that you’ll need to create/modify are:
src/plugins/auth-listener.js src/plugins/axios.js src/store/index.js
And of course nuxt.config.js
. Please check out the example
repository created for this
article and get the code artifacts there.
There are two notes that I’d like to add about Austin’s article 1:
- The use of the
__session
cookie. It turns out that when I try to host my server side app on Firebase functions and client side app on Firebase hosting, the only cookie that’s able to appear on both server and client side is the__session
cookie. I’ve tried to user other cookie names withlang
,__lang
, for my i18n work of keeping the user’s choice of language, but they have all failed to appear with the request sent to the server end on Firebase functions. (I should have read Austin’s article more carefully…) So in my example repo I changed the vuex store to use JSON encoding and decoding to handle the__session
cookie. - How to initialize
firebase-admin
. This step becomes really tricky in the SSR setup. Firebase admin must be initialized insidenuxt.config.js
, before theconfig
blob, anddotenv
needs to be initialized beforefirebase-admin
to provide access to environment variables. This same initialization needs to be done in both the rootnuxt.config.js
and thefunctions/index.js
file. I also tried doing the initialization insrc/store/index.js
orsrc/server/index.js
and neither works. In the failure mode, your SSR app would return the famous error message:ERROR Your API key is invalid, please check you have copied it correctly.
Firebase functions
Now we’ve finished most of the coding for our Nuxt.js app, but we need to give it another entry point for Firebase functions. There have been many articles on how to deploy Nuxt.js’s server side app onto Firebase functions, but I was mainly stuck at two points:
- How to make the built artifact inside
.nuxt
available for thefunctions/
directory. There has been example Github repositories using different ways of copying.nuxt
, either by specifying abuildDir
field innuxt.config.js
, or by copying them explicitly before deploying to Firebase. After trial and error, I agree with 4 that thebuildDir
specification doesn’t work quite well: there may be dependencies that won’t work (I suspect@nuxtjs/firebase
is one of them) if the.nuxt
directory is not at the same level as thepackage.json
file. - How to make environment variables available for server side and client side
apps. This also took me a long time to figure out:
process.env.*
environment variables are only available to server side app, and only those specially exposed variables in theenv
field ofnuxt.config.js
is available on client side. This is a good feature for security concerns, but it just took me too long to figure out. Another issue that took me a long time to figure out was that thedotenv
config needs to be initialized both insideindex.js
infunctions/
andnuxt.config.js
. I only initialized it insrc/server
before, and it took me a long time to debug and realize that my environment variables were not setup inside Firebase functions.
So, the correct structure to have for the functions/
directory should be:
$ ls -a functions
.nuxt/ index.js package.json
And functions/
should have similar (not necessarily all) dependencies as the
root project dir. So to add the dependencies, we can do:
yarn add dotenv firebase firebase-admin @nuxtjs/firebase @nuxtjs/axios jwt-decode nuxt nuxt-buefy
Work on Firebase Hosting
Now it’s time to think about what portion of the built Nuxt.js app are static
files that should be deployed to Firebase hosting. There are also many different
ways of doing this according to 45. But after trial and error, I found out
that only the .nuxt/dist/client/
directory needs to be copied to the
public/_nuxt/
directory that is waiting to be deployed to Firebase Hosting.
So, the correct directory structure for Firebase Hosting should be:
$ ls -a public
# _nuxt/* should be copied from .nuxt/dist/client/*
_nuxt/ favicon.ico
Wrapping up
Deploy with one command
So, to wrap it up, by using package.json
and firebase.json
, we can copy
around necessary files and accomplish a one line command for deployment:
package.json
:
"scripts": {
"dev": "yarn install && yarn --cwd functions install && cross-env NODE_ENV=development HOST=0.0.0.0 PORT=8080 nodemon src/server/index.js --watch src/server",
"build": "yarn install && yarn --cwd functions install && nuxt build && rm -rf functions/.nuxt && mv .nuxt functions && rm -rf public && mkdir public && cp -r functions/.nuxt/dist/client public/_nuxt && cp -a src/static/. public/",
"start": "cross-env NODE_ENV=production node src/server/index.js",
"ssr": "firebase serve",
"preparedev": "cp firebase.json.development firebase.json && cp firebase_key.json.development firebase_key.json && cp .env.development .env && cp -f firebase_key.json .env functions/",
"prepareprod": "cp firebase.json.production firebase.json && cp firebase_key.json.production firebase_key.json && cp .env.production .env && cp -f firebase_key.json .env functions/",
"stage": "cp firebase.json.development firebase.json && firebase use development && firebase deploy",
"deploy": "cp firebase.json.production firebase.json && firebase use production && firebase deploy",
"clean": "rm -rf public/ node_modules/ functions/node_modules .nuxt functions/.nuxt"
},
firebase.json.{development,production}
:
They are two files, just differ by the dev
or prod
predeploy step.
{
"functions": {
"source": "functions",
"predeploy": [
"yarn prepare{dev,prod} && yarn --cwd functions install && yarn install && yarn build"
]
},
"hosting": {
"predeploy": [
"rm -rf public && mkdir public && cp -r functions/.nuxt/dist/client public/_nuxt && cp -a src/static/. public/"
],
"public": "public/",
"ignore": ["firebase.json*", "**/.*", "**/node_modules/**"],
"rewrites": [
{
"source": "**",
"function": "nuxt",
}
]
}
}
Example Repository and Demo webapp
The example repository accompanying this tutorial is located at https://github.com/1919yuan/nuxt-firebase-example. The sample deployed webapp is at https://nuxt-firebase-example-1919yuan.web.app.
Tips for Debugging
Run feature development with yarn dev
The yarn dev
script setup the universal Nuxt.js app with a single
process. This comes with hot reload that enables you to iterate on feature
development quickly.
Emulate Firebase environment with yarn ssr
This yarn ssr
command is essentially firebase serve
, which starts Firebase
functions emulator and a static webserver together. This is the closest
environment to Firebase you can get without actually deploying. It is useful for
debugging issues that only arise in SSR mode, like cookie out-of-sync
issues. The drawback of this mode is that changes in src/
cannot be
hot-reloaded.
ERROR Your API key is invalid, please check you have copied it correctly
This is the bug that bothered me for the longest time. There may be multiple issues that can cause this error message to come about. Some examples are:
- Environment variables set in
src/
are lost from the entrypoint infunctions/
.dotenv
also need to be initialized twice. firebase-admin
is not initialized at the right places: it must be initialized once insidenuxt.config.js
and once insidefunctions/index.js
.@nuxtjs/firebase
is not installed insidefunctions/
.
Cookie not set on client side
It may be a headache that sometimes a cookie you set in client side doesn’t
propagate to Firebase functions (i.e. the server side). You can only debug such
issue using firebase serve
and try printing out the cookies at different code
locations. cookie-universal-nuxt
is the solution for me. Using that package,
the __session
cookie I set appears on both server and client side.
Cookie not sent to server side
This issue was noted in Austin’s article 1, where he pointed out that if
hosting on Firebase functions, only the cookie with name __session
goes
together with the http request to Firebase functions.
buildDir
in nuxt.config.js
There are some tutorials using the buildDir
option to output the .nuxt
build
directory out to functions
, but I had the same observation with 5, that if
the buildDir
is not the default .nuxt
directory, lots of UI / functions
simply don’t work. Therefore we have to move it with the build scripts.
_nuxt
directory
Only the .nuxt/dist/client
directory is relevant for Firebase hosting. There
are some sample code that moves the entire .nuxt/
directory to Firebase
hosting, that is not necessary.