Deploy Nuxt.js (SSR) to Firebase Cloud Functions

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 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.


npm run build

NOTE: .nuxt is generated


cd /projects/journey-firebase

Setup Firebase Hosting and Functions

Copy .nuxt to functions/.nuxt.

cd functionsln -s /projects/journey-nuxt/.nuxt/ .

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


cd functionsnpm install firebasenpm install nuxt-start

NOTE: both nuxt or nuxt-start works

Depending on your nuxt project dependencies (/projects/journey-nuxt/package.json), some packages are required on server side as well

  • bootstrap-vue
  • vuelidate


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 variablesprocess.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=""

Local Development

firebase server

Access: http://localhost:5000/


Deploy Hosting

firebase deploy --only hosting

Set Runtime Configuration

firebase functions:config:set nuxtssr.runtime_api_url=""

Deploy Functions

firebase deploy --only functions:nuxtssr


Web Setups

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

cd publiccp -R /code/vue/journey-nuxt/static/* .

Our you could use symbolic links.

cd publicln -s /code/vue/journey-nuxt/static/* .


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 publicln -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);})


  • 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.

❤️ Is this article helpful?

Buy me a coffee ☕ or support my work via PayPal to keep this space 🖖 and ad-free.

Do send some 💖 to @d_luaz or share this article.

✨ By Desmond Lua

A dream boy who enjoys making apps, travelling and making youtube videos. Follow me on @d_luaz

👶 Apps I built

Travelopy - discover travel places in Malaysia, Singapore, Taiwan, Japan.