Logo
Haythem Chibani
Published on

Deploy Next Js App with Server-side Rendering (SSR) to Firebase at ~0$ cost

Authors

Using Next.Js comes with plenty of usefull features one of which is Server-side Rendering that offers us extremely fast loading speed , improves user experience and ofcourse it helps search engines to easily index & crawl content . But to be able to do that our Next.Js App needs a server to handle server rendering. So in this Article , I will demonstrate how to deploy a Next.Js app with SSR to firebase using cloud function & firebase hosting .

Bootstrapping our Application

npx create-next-app firebase_example

Now lets update our pages/index.js with the following code :

export default function Home(props) {
  return (
    <div
      style={{
        height: "100vh",
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
      }}
    >
      {props.ssrWorking ? <h1>SSR Working 🔥!</h1> : <h1>SSR NOT WORKING !</h1>}
    </div>
  );
}
export async function getServerSideProps() {
  return {
    props: { ssrWorking: true },
  };
}

What we did here is calling getServerSideProps function and passing ssrWorking prop to our Page , now we should be able to tell if SSR is going to be working when deployed to production .

Prepare our firebase Project :

We go straight to firebase Console Select Add project and name it firebase-example

02

Select continue for step 2 and step 3 and click on create Project. The following page appears confirming the project is successfully setup ♨.

Next we need to upgrade it to Blaze (Pay as you go) Plan in order to use cloud Function Functionality. This is the only configuration we need on the firebase console.

Firebase setup for our Next js App

Now we go back to our Next.js Project. We start by installing the firebase tools to be able to run firebase CLI commands.

npm install -g firebase-tools

Then we connect our Next.js project with the firebase project we just created by the following command.

firebase login

Now we have access to all the firebase projects associated with the login account. We now initialize firebase on our project using

firebase init

01
  1. Select Hosting configuration for now , we will add functions manually in the next step 1
  2. Select existing projects as we have already created project through firebase console.
  3. Select the project we just created on the console.
  4. Select public and No for subsequent questions.

Firebase functions Configuration

Now we edit our firebase.json file to our requirements as follows :

{
  "hosting": {
    "public": "public",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "rewrites": [
      {
        "source": "**",
        "function": "nextServer"
      }
    ]
  },
  "functions": {
    "source": ".",
    "runtime": "nodejs16"
  }
}

public defines the files in our project directory that should be deployed and ignore defines the files that should be ignored during deployment.

We include the rewrite rule in the case if the request to files or directories through URL doesn’t exist on the public folder. In such a case we execute nextServer cloud function to determine the response.

source indicates the location of our nextServer function.

runtime indicates the Node.js runtime version to run our nextServer function.

Cloud function Setup

We will be creating our custom Server in function.js in the Root of project .

const { https } = require("firebase-functions");
const { default: next } = require("next");

const isDev = process.env.NODE_ENV !== "production";
const server = next({
  dev: isDev,
  //location of .next generated after running -> npm run build !
  conf: { distDir: ".next" },
});
const nextjsHandle = server.getRequestHandler();
exports.nextServer = https.onRequest((req, res) => {
  return server.prepare().then(() => nextjsHandle(req, res));
});

Here we are checking if we are running the application in a development or production environment to later provide the information to the next function and then we connect our custom Server to our Next.js Application by providing the location of .next folder generated after running npm run build command in our root Next.js application to the conf option of the next function.

server.getRequestHandler returns a request handler which we can use to parse all HTTP requests.

server.prepare makes our Next.js build code(.next) ready to run on our custom server for handling SSR.

Preparing for deployment

First of all let’s delete the public/index.html file generated when we ran firebase init

Right after that let’s install our dependencies running :

npm i firebase-admin firebase-functions cross-env

Now we’ll be configuring our package.json :

{
  "name": "firebase_example",
  "version": "0.1.0",
  "private": true,
  "main": "function.js",
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "deploy": "next build && cross-env NODE_ENV=production firebase deploy --only functions,hosting"
  },
  "dependencies": {
    "cross-env": "^7.0.3",
    "firebase-admin": "^11.3.0",
    "firebase-functions": "^4.1.0",
    "next": "13.0.4",
    "react": "18.2.0",
    "react-dom": "18.2.0"
  },
  "devDependencies": {
    "eslint": "8.28.0",
    "eslint-config-next": "13.0.4"
  }
}

Here we added our function file function.js on the main key and added our deployment script to Build and Deploy ✌.

Deployment

Now everything is setup and we are ready to deploy our Application

Lets run npm run deploy 04

Everything works great 😍 we can simply now access our hosted App with the link provided in the console 🔥


CI/CD configuration

For the next part we’re going to be setting up our github actions for continuous integration & deployment .

For that we are going to need our firebase service account key to authenticate . 03

In the firebase console we navigate to Project settings and then we go to Service accounts so we can generate our private key , now we have downloaded a json file containing our access config .

Lets copy the content of the json file and add it as a github secret named FIREBASE_SERVICE_ACCOUNT .

Now we are good to go straight and create our github actions

Let’s create a folder named .github and add 2 files inside

firebase-ci-pr.yml
name: Deploy to Firebase Hosting on PR
'on': pull_request
jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: npm ci && npm run build
      - uses: www-norma-dev/firebase-hosting-functions-deploy@main
        with:
          repoToken: ${{ secrets.GITHUB_TOKEN }}
          firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
          projectID: fir-example-11ebb
firebase-cd-main.yml
name: Deploy to Firebase Hosting on main merge
'on':
  push:
    branches:
      - main
jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: npm ci && npm run build
      - uses: www-norma-dev/firebase-hosting-functions-deploy@main
        with:
          repoToken: ${{ secrets.GITHUB_TOKEN }}
          firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
          projectID: fir-example-11ebb
          channelId: live

The first github action deploys our PR to a preview channel that expires in 7days and comments the URL on the PR .

The second github actions deploys our App on Live channel when we merge a PR to the main branch .

Don’t forget to update the project ID with yours ( available in firebase console Project settings under general settings . )

We now have our project setup for manual deployment running npm run deploy command as well as a CI/CD Pipeline to handle our PRs and production on main branch merge .


Thats it for today 😀, we now have a Next.Js App deployed to firebase hosting with cloud function to handle Server-side Rendering , with this approach you can use Next.Js to its full potential including Incremental-static-regenration as well as API-routes , Goodluck on your journey and happy coding 👊