Quick Nuxt.js SSR prototyping with Firebase Cloud Functions

Production grade superb CDN + no backend + SSR, all-in-one

tl;dr: Here’s an example repo of Nuxt.js + Firebase Cloud Functions + Firebase Hosting. This stack is suitable for prototype and production use
https://github.com/williamchong007/nuxt-ssr-firebase

1. Introduction

I like vue.js a lot for its simplicity, and we use Nuxt.js a lot in both LikeCoin and for personal side projects. It’s best feature is its simple server-side-render (SSR) functionality and routing model.

However, running SSR means I have to spin up a node.js server, which can be troublesome at times. For prototyping, we certainly don’t want to spend too much time messing with VMs/docker/k8s; on the other hand, when a prototype made it into production, one would always regret not spending enough time on planning scalability and availability. Especially in LikeCoin, where we have a small team developing many MVPs in high velocity, very often prototypes have to be ready for production in a short time.

I find Firebase a very suitable platform in my case. It is simple enough to use for rapid prototyping, yet powerful enough to scale for production. There was an old tutorial made by Firebase describing how to deploy Nuxt.js to Firebase, but it was very much outdated so I decided to write an updated one.

Goal: We would write our Nuxt.js app in universal mode (SSR), deploy the Nuxt.js app onto Firebase Functions, and serve our static assets with Firebase Hosting with CDN.

2. Write our Nuxt.js app

Init the Nuxt.js app with npx:

npx create-nuxt-app firebase-ssr-app/src

For the create-nuxt-app options, pick what is useful in your case, but make sure you picked Universal mode for SSR support. For simplicity I chose none for most options. Notice how I initialized the Nuxt.js project inside subdirectory /src .

Write the Nuxt.js app in development mode as usual. If you don’t know how powerful Nuxt.js can be yet, here’re some examples.

3. Init the Firebase project

Create a Firebase project in Firebase console. Also make sure we have firebase-tool installed.

npm install -g firebase-tools

Init a Firebase project inside the project directory, in our case firebase-ssr-app/

cd firebase-ssr-app
firebase init

Select Functions and Hosting feature, then select the project we just created in Firebase console as default project. For other options, the default ones are good enough. After everything is done, firebase-tool would create 2 folders and 1 config file in firebase-ssr-app/ for us. functions/ ,public/ and firebase.json.

P.S. In case anything above feels unclear or confusing, Firebase has a good tutorial on how to initialize a project with firebase-cli.

4. Create the Nuxt renderer cloud function

Since Firebase Cloud Functions are powered by express, we can return a simple nuxt.render middleware and it’s done.

ssrapp.js

I used nuxt-start instead of nuxt. nuxt-start runs in production mode and is more lightweight, which makes it more suitable for cloud functions environment. I also required a nuxt.config.js which will be copied from src/ directory in next step.

The dev config option would build the Nuxt.js app in hot-reload mode, which could be disasterous since the cost of running cloud functions depends on execution time. We explicitly override it with false.

The debug option controls whether server side errors are displayed in webpage. It would be useful for debugging our deployment, but for now we leave it as false.


In functions/package.json , since Nuxt.js runs in node.js version ≥ 8, we need to specify:

"engines": {
"node": "8"
},

Also for any dependencies that needs to be run in server-side e.g. vuex, nuxtjs plugins, they need to be specified in the package.json in functions/ . Here’s an example of what it should look like. Remember to run npm insatll afterwards!

5. Modify the firebase.json config for deployment

For the final step, what we want to achieve is when we run firebase deploy to deploy our Nuxt.js app, the following should happen:

  1. Nuxt.js app in src/ should be built for production
  2. nuxt.config.js and the Nuxt.js server build should be copied into functions/ for Firebase Cloud Functions deployment.
  3. Nuxt.js client build and other static assets should be copied into public/ for Firebase Hosting deployment.
  4. Deploy the Firebase Cloud Functions and Firebase Hosting

Here’s an example of a firebase.json that could do the job.

firebase.json

functions.predeploy hook runs everytime before a new version of cloud functions is deployed. Here we run npm run build inside src/ directory which would build the Nuxt.js app. We then copy the _nuxt/ and nuxt.config.js folder into functions/nuxt and functions/nuxt.config.js .

Similarly, hosting.predeploy hook runs before a new version of hosting is deployed. Since functions.predeploy runs before hosting does, we can just copy the Nuxt.js client build in src/nuxt/dist/client/ into public/_nuxt/, and static assets in src/static/ into public/. _nuxt here is the default public path for Nuxt.js app scripts, it is modifiable in nuxt.config.js if it does’t look intuitive to you.

In hosting.rewrites, we redirect all traffic to ssrapp, which is the Nuxt render function we just defined in previous step.

6. Deploy your app

One just simply type:

firebase deploy

Then our Nuxt.js app would be deployed to both Firebase Cloud Functions and Firebase Hosting. Hooray!


The above instructions only cover the essential steps and concepts. For a complete config/code example, please visit my sample repo here. Inside the repo I also added extra steps for lint, babel, and a working CircleCI configuration.


Extra tips:

If you see a Nuxt.js server render error page like this, and is wondering how to debug:

Not very informative error page

Setting debug: true in your cloud function’s Nuxt config would make the error more verbose, i.e.

const config = {
...nuxtConfig,
dev: false,
debug: true,
buildDir: 'nuxt',
};
const nuxt = new Nuxt(config);
more verbose error page for debugging

Remember to turn debug back to false when you are done.


Give me some LIKE if you like this article!
Edit (10/05/2019): I made a mistake in firebase.json, cp -r src/nuxt/dist/client public/_nuxt should be cp -r src/nuxt/dist/client/ public/_nuxt. The correct result looks like public/_nuxt/*.(js|css), so that these asset files are served at default path ${your_domain}/_nuxt/ directly.