Deploy Nuxt.js (SSR) to Firebase Cloud Functions

February 14, 2020

I put Nuxt and Firebase project in separate directories

  • /projects/journey-nuxt
  • /projects/journey-firebase

Build Nuxt

cd /projects/journey-nuxt

I assume you setup your Nuxt.js project with universal render mode(support both SSR and client).

npm run dev

I prefer to have separate buildDir for development and production, because I use symbolic link to link the .nuxt directory to firebase project (running npm run dev will overwrite the .nuxt directory created by npm run build, since they both share the same directory).

If you are using axios to fetch data from the same server, you will realize

  • On Nuxt development (npm run dev), the data is on http://localhost:3000 by default
  • On Firebase local development (firebase serve), the data is on http://localhost:5000 by default
  • On Firebase production, the data is on https://PROJECT.firebaseapp.com or custom domain name.
  • If you switch from Firebase to another environment like Docker, the url for the data might change again.

Refer to dotenv and Nuxt Axios Runtime Environment baseUrl.

Build

npm run build

NOTE: .nuxt is generated

Firebase

cd /projects/journey-firebase

Setup Firebase Hosting and Functions

Copy .nuxt to functions/.nuxt.

cd functions
ln -s /projects/journey-nuxt/.nuxt/ .

NOTE: You can either use symbolic links or copy the content

Copy dependencies from /projects/journey-nuxt/package.json to functions/package.json

nuxtssr

Create Nuxt SSR app on cloud functions.

Edit functions/index.js

const functions = require('firebase-functions');

const config = functions.config();
// convert config to node environment variables
process.env.RUNTIME_API_URL = config.nuxtssr.runtime_api_url
// process.env.RUNTIME_API_URL = 'http://localhost:5000'

const { Nuxt } = require('nuxt')

const nuxtConfig = {
  dev: false,
  // debug: true
}

const nuxt = new Nuxt(nuxtConfig)

exports.nuxtssr = functions.https.onRequest(async (req, res) => {
  await nuxt.ready();
  nuxt.render(req, res);
})

Edit firebase.json and add the following configuration.

{
  "hosting": {
    "rewrites": [
      {
        "source": "**",
        "function": "nuxtssr"
      }
    ]
  }
}

NOTE: This will route all require to nuxtssr. This setup is sub-optimal, but it will do for now.

Runtime Environment Variables

As mentioned earlier, we might need to setup some runtime environment variables for use cases such as axios.

For local development (firebase serve), create functions/.runtimeconfig.json.

{
  "nuxtssr": {
    "runtime_api_url": "http://localhost:5000"
  }
}

For production

firebase functions:config:set nuxtssr.runtime_api_url="https://PROJECT.firebaseapp.com"

Local Development

firebase server

Access: http://localhost:5000/

Deployment

Deploy Hosting

firebase deploy --only hosting

Set Runtime Configuration

firebase functions:config:set nuxtssr.runtime_api_url="https://PROJECT.firebaseapp.com"

Deploy Functions

firebase deploy --only functions:nuxtssr

Access: https://PROJECT.firebaseapp.com

Web Setups

Copy /projects/journey-nuxt/static to public directory.

cd public
cp -R /code/vue/journey-nuxt/static/* .

Our you could use symbolic links.

cd public
ln -s /code/vue/journey-nuxt/static/* .

Optimization

static /_nuxt request

There are a few more things to be done for improved performance.

You shall notice the client script make requets to /_nuxt/2330881c6ecdfac16aec.js, which is currently served by nuxtssr cloud functions. We could copy content of .nuxt/dist/client to public/_nuxt, or use a symbolic link.

NOTE: This way we could make use of CDN/EDGE features of Firebase Hosting, not to mentioned reduce cost of cloud functions.

cd public
ln -s ../functions/.nuxt/dist/client/ _nuxt

Edit firebase.json

{
  "hosting": {
    "headers": [
      {
        "source": "**/_nuxt/*.*",
        "headers": [{
          "key": "Cache-Control",
          "value": "public, max-age=31536000"
        }]
      }
    ]
  }
}

cache-control for dynamic content

Edit functions/index.js

exports.nuxtssr = functions.https.onRequest(async (req, res) => {
  res.set('Cache-Control', 'public, max-age=300, s-maxage=600')
  await nuxt.ready();
  nuxt.render(req, res);
})

Cache-Control

  • public: Marks the cache as public. This means that both the browser and the intermediate servers (meaning the CDN for Firebase Hosting) can cache the content. (Default is private)

  • max-age: how many seconds browser and CDN can cache the content.

  • s-maxage: how many seconds CDN can cache the content.

For max-age and s-maxage, set their values to the longest amount of time that you’re comfortable with users receiving stale content. If a page changes every few seconds, use a small time value. However, other types of content can be safely cached for hours, days, or even months.

This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.