Deploying a React.js and Next.js project to Firebase

We are in one of the best moments as developers, living among wide range of technologies, tools and trends that every day only grows and grows, and if you are passionated about learning and applying technologies during your spare time, today is the time.

Today we will focus precisely on ReactJS and the performance we can provide to our applications using the scalability of Firebase with Cloud Functions itself.

In addition, we will use the NextJS framework to server rendering, which will help us to easily configure and solve SEO issues but, more importantly, the lower degree of latency between server and client to offer the users the best experience.

Let’s start!

We need first to install Next.js in our work environment:

npm install -g create-next-app

Followed We create a project named “firebase-nextjs”:

create-next-app firebase-nextjs: this command will create the project but it will also install the react and react-dom dependencies, which can be seen in the package.json file.

We run yarn dev and our project will run, by default, on port 3000 (localhost:3000).

NextJS will search the corresponding file in the pages folder for each route, for example: the path “/” indicates that the file pages/index.js will be searched.

The framework is built on React, it maintains a structure appropriate to it, which is why we can see a folder called “components”, where each component we want to use for our views will be store.

So, if we use NextJS and React, why not to add Material-UI?

Let’s install locally Material-UI: npm install @material-ui/core.

We will create two very simple pages to observe the behavior of these three frameworks made to make life easier!

In the folder pages we create “page1.js” and “page2.js”.

We add the following code to pages/page1.js:

import Card1 from '../components/card1'

const Page1 = () => (
  <div>
    <Card1 />
  </div>
)
export default Page1

In the folder components we create “card1.js” and add any of the examples in Material-UI in the cards section and personalize it in our own way.

To see the content of the page page1.js we can go to “http://localhost:3000/page1” or use Link of NextJS to redirect us.

We do the same steps for “page2” and “card2”.

Up to this point, we should have a NextJS application working perfectly with React components.

We are ready to enter the most interesting part of this post. We are going to structure our project according to the guide of James Hegedus.

Therefore, at the root of our project we create a folder called “src”, inside this folder you will find another another called “app”, where our entire application files will be stored, we might move all the files to the “app” folder.

We will go to the firebase console and create our project.

We will be presented the general panel of the project, as you can see, Firebase gives us Authentication, Database, Storage, Hosting, Functions and ML Kit, each of them very interesting, but we will focus on Functions.

Why Functions? If we do not use NextJS, and it is enough that in our source code of the application we see only some ‘<script>’ tags then the option would be Hosting, but we want to get the server rendering and the magic of NextJS that is really amazing.

Next we run the following commands in the root of our project:

> yarn global add firebase-tools
> firebase login
> firebase init

When we run firebase init, a series of questions will be displayed:

  • Which Firebase CLI features do you want to setup for this folder? | Select “Functions” and “Hosting”.
  • Select a default Firebase project for this directory: | Select the project that you created earlier in the firebase console.
  • What language would you like to use to write Cloud Functions? | In my case is “Javascript”.
  • Do you want to use ESLint to catch probable bugs and enforce style? | Write “N”.
  • Do you want to install dependencies with npm now? | At this time it is not necessary, write “n”.
  • What do you want to use as your public directory? | Write “src/public”, two files will be created that will be created to show the first view in case of not finding any and the other in case of error.
  • Configure as a single-page app (rewrite all urls to /index.html)? Write “N”, in our case it should be a more complex application.

Ready!

We will find in the root of our project a folder called “functions”, just move it inside “src” folder.

In our file firebase.json we remove the existing code and update it by the following:

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

In the package.json of the functions folder we add the following dependencies (after several tests I consider that they are the versions that present fewer errors):

"@material-ui/core": "^1.4.3",
"classnames": "^2.2.6",
"firebase-admin": "~6.0.0",
"firebase-functions": "^2.0.0",
"next": "^6.1.1",
"prop-types": "^15.6.2",
"react": "^16.4.2",
"react-dom": "^16.4.2"

In the case of dependencies “classnames” and “prop-types”, they are needed for this example that we are developing, also as a tip, each dependency we need for our project must go in this file as well as src/app/package.json , therefore, also add the dependencies to src/app/package.json.

To make sure that NextJS runs successfully in Cloud Functions, we remove the contents of functions/index.js by:

const functions = require("firebase-functions")
const next = require("next")

var dev = process.env.NODE_ENV !== "production"
var app = next({ dev, conf: { distDir: "next" } })
var handle = app.getRequestHandler()

exports.next = functions.https.onRequest((req, res) => {
  console.log("File: " + req.originalUrl) // log the page.js file that is being requested
  return app.prepare().then(() => handle(req, res))
})

Now we proceed to install the dependencies in functions and in app, within src/functions and src/app run yarn or npm install depending on the manager you use.

We create a .gitignore inside the functions folder and add only “next”.

We need our material-ui components to be displayed correctly, thanks to NextJS we should only add the following to src/app/next.config.js:

const path = require('path')
const glob = require('glob')

module.exports = {
  distDir: "../functions/next",
  webpack: (config, { dev }) => {
    config.module.rules.push(
      {
        test: /.(ico|gif|png|jpg|jpeg|svg|webp)$/,
        use: [
          {
            loader: 'emit-file-loader',
            options: {
              name: 'dist/[path][name].[ext]'
            }
          },
          {
            loader: 'file-loader',
            options: {
              name: 'dist/[path][name].[ext]'
            }
          }
        ]
      },
      {
        test: /.(css|scss)/,
        loader: 'emit-file-loader',
        options: {
          name: 'dist/[path][name].[ext]'
        }
      },
      {
        test: /.css$/,
        use: ['babel-loader', 'raw-loader', 'postcss-loader']
      },
      {
        test: /.s(a|c)ss$/,
        use: ['babel-loader', 'raw-loader', 'postcss-loader',
          { loader: 'sass-loader',
            options: {
              includePaths: ['styles', 'node_modules']
                .map((d) => path.join(__dirname, d))
                .map((g) => glob.sync(g))
                .reduce((a, c) => a.concat(c), [])
            }
          }
        ]
      }
    )
    return config
  }
}

Let’s update our package.json that is in the root of the project with the scripts with which we will handle the application:

{
  "name": "firebase-nextjs",
  "version": "1.0.0",
  "license": "MIT",
  "scripts": {
    "install": "yarn build-all",
    "next": "yarn build-firebase && cd "src/app" && yarn && yarn dev",
    "preserve": "yarn build-all",
    "serve": "firebase serve",
    "predeploy": "yarn build-all",
    "deploy": "firebase deploy",
    "build-all": "yarn build-next && yarn build-firebase",
    "build-next": "cd "src/app" && yarn && yarn build",
    "build-firebase": "cd "src/functions" && yarn"
  }
}

Now, in the root project run yarn init and then you have the next options:

yarn next: We run the project locally by port 3000 by default.

yarn build-all && firebase deploy –only functions: we build our project for production and deploy to firebase.

With all the steps above the application must run successfully, the test project is available in github, so you can compare, to get a better guide and understanding of the code.

Conclusion

This is a contribution that is made from the same learning obtained, so we are willing to feedback and/or answer your concerns about the technology and the project as such.

We try to be aware of the frameworks and tools of the moment to get the most out of each one, so you can expect more from this type of blogs.

Enjoy it!

Support:

Resources we used:

  • NextJS: 6.1.1
  • React: 16.4.2
  • Firebase functions: 2.0.0
  • Material-UI: v1.5.0

References: