What I struggled with Next.js using Firebase Hosting (and enable SSR)
Tags: nextjs, firebase, ssr
Published: 2021-02-27 23:19:03

## Overview
Based on this article:

[Deploying Next.js Application on Firebase Platform using Cloud Function with Firebase Hosting. by Dipesh K.C.](https://medium.com/wesionary-team/deploying-next-js-application-on-firebase-platform-using-cloud-function-with-firebase-hosting-920157f03267)

Here's my sample code:

https://github.com/rowaxl/nextjs-with-firebase-hosting-functions

## Background
Next.js is one of the most popular frameworks to make a React app with both Server Side Rendering and Static Site. So when the project started, I planned to use Next.js to generate a static website that will work fine, which means Firebase Hosting is quite fine(cannot choose Vercel, because it using a closed source library). After it was deployed, this project needed SSR-related features, like persist save of route.

This [article](https://medium.com/wesionary-team/deploying-next-js-application-on-firebase-platform-using-cloud-function-with-firebase-hosting-920157f03267) helps me a lot.


## Create a Next.js App
First of all, start with creating the boilerplate Next.js app.

```sh
$ yarn create next-app

or

$ npx create-next-app
```

`create-next-app` module must be installed on global.

```jsx
export default function Home(props) {
  return (
    <div>
      <div>
        {
          props.isSSR ? (
            <h2>
              SSR Working
            </h2>
          ) : (
            <h2>
              SSR Not Works
            </h2>
          )
        }
      </div>

      <div>
        {
          props.isStatic ? (
            <h2>
              Static generated
            </h2>
          ) : (
            <h2>
              is Not Static
            </h2>
          )
        }
      </div>
    </div>
  )
}

// currently, this method occurs error
export async function getServerSideProps() {
  return { props: { isSSR: true } }
}

// currently, this method occurs error too!
export async function getStaticProps() {
  return { props: { isStatic: true } }
}
```

Here's our base code for testing with SSR (Server Side Rendering) and SSG (Static Site Generation). 

`getServerSideProps` and `getStaticProps` looks like doing same works. It fetches data **before** page renders, and provides data as props.

Before start, you cannot use `getServerSideProps` and `getStaticProps` at a same time.

### getServerSideProps (SSR)
It will be called when Next.js server got a request. It slow than `getStaticProps`, because it occurs every time get a request, but if you need to pre-render a page with dynamic data, definitely `getServerSiteProps` makes it convenient to deal with it.
It only runs on server-side and routing by `next/link` and `next/router` runs it every time.

### getStaticProps (SSG)
It generates static HTML files when `next export` runs. With `getStaticPaths`, you can use specific dynamic data to use.

If you need details and sample codes, please check [official documents](https://nextjs.org/docs/basic-features/data-fetching#when-should-i-use-getserversideprops).

## Deploy Next.js App with only Hosting (SSG)

### Ready Firebase Projects
At first, I used SSG, because [Firebase Hosting supports only static files](https://firebase.google.com/docs/hosting/use-cases).

So, let's start with initializing Firebase project.

When your project is deployed, add a new web app.

Then install Firebase CLI in your global

```
npm install -g firebase-tools

or

yarn global add firebase-tools
```

And click `Next` and `Continue to the console`. It will be deployed little bit a later.

### Change codes a bit
Before execute `next export`, remove the `getServerSideProps` first. You can check the result of `getStaticProps` with `next dev` command.

```jsx
// pages/index.js

export default function Home(props) {
  return (
    <div>
      <div>
        {
          props.isSSR ? (
            <h2>
              SSR Working
            </h2>
          ) : (
            <h2>
              SSR Not Works
            </h2>
          )
        }
      </div>

      <div>
        {
          props.isStatic ? (
            <h2>
              Static generated
            </h2>
          ) : (
            <h2>
              is Not Static
            </h2>
          )
        }
      </div>
    </div>
  )
}

export async function getStaticProps() {
  return { props: { isStatic: true } }
}
```

So, it will shows like below when it runs.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fkmkiutx9eio16c0h72e.png)

Run `yarn build` and `yarn next export` to create static files. It will be exported to `out` as a default folder.

You can see `out/index.html` already has the texts that we expected:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kti5310239zdl3n42gzu.png)

When exporting success, let's try to deploy it to the Firebase Hostings.

```sh
$ firebase login

// login page will show up.

$ firebase init

// select the hosting only at this time.
```

Then, edit `firebase.json` a little bit.

```json
{
  "hosting": {
    "public": "out",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ]
  }
}
```

OK, it looks ready for deployment. Let's try `firebase deploy`.
If anything goes wrong, it will say `✔  Deploy complete!` and the Hosting URL for us.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7hrxgc9wx6e64t0zduns.png)

It looks like it works perfectly!

## Deploy Next.js App with Functions (SSR)
Next step, what we need to run our Next.js App on SSR is Firebase Functions.
It means you HAVE TO UPGRADE your billing plan to Blaze. Also, install `firebase-functions` and `firebase-admin` modules for deploying.

### Update to use `getServerSideProps`
```jsx
  return (
    <div>
      <div>
        {
          props.isSSR ? (
            <h2>
              SSR Working
            </h2>
          ) : (
            <h2>
              SSR Not Works
            </h2>
          )
        }
      </div>

      <div>
        {
          props.isStatic ? (
            <h2>
              Static generated
            </h2>
          ) : (
            <h2>
              is Not Static
            </h2>
          )
        }
      </div>
    </div>
  )
}

export async function getServerSideProps() {
  return { props: { isSSR: true } }
}
```

WARNING: You should not use `getServerSideProps` and `getStaticProps` at the same time! This will show you build error.

### Add Next server functions

```js
// function.js
const { https } = require('firebase-functions');
const { default: next } = require('next');

const isDev = process.env.NODE_ENV !== 'production';

const server = next({
  dev: isDev,
  conf: { distDir: '.next' },
});

const nextjsHandle = server.getRequestHandler();
exports.nextServer = https.onRequest((req, res) => {
  return server.prepare().then(() => nextjsHandle(req, res));
});
```

This one will handle requests toward to client, render pages, and return as a response.

And, update your `firebase.json` like below

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

Also, update `package.json` for deploy command

```json
{
  "name": "firebase-deploy",
  "version": "0.1.0",
  "main": "function.js",
  // ...
}
```

Don't forget to remove `public/index.html`, which is generated by firebase-tools

What's going on here is like below:
- The initial request goes to hosting
- When the request routes are not the `/`, then rewrites the request to functions named nextServer (hostings → functions)
- `nextServer` handles request, renders it as a Next.js server, and returns the response.

After edit, remove `.out` folder and try `next build`, `firebase  deploy --only functions:nextServer,hosting`

Then, you can see the result like below

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9hclji669fi5efabix6f.png)

🎉 SSR enabled! 🎉

## Troubles that I encountered

### 400 unknown error in deployment

The first time deploying `nextServer` function, it worked fine. But the next moment, it shows `400 unknown error` and failed when I tried to upload the new version.

When I run deploy command with `--debug` option, it showed the logs like below:
```xml
<Code>
  EntityTooLarge
</Code>
<Message>
  Your proposed upload is larger than the maximum object size specified in your Policy Document.
</Message>
<Details>
  Content-length exceeds upper bound on the range
</Details>
```

Initially, my Next app was over 200 MB after packaged. I believe [Firebase Functions supports 1GB of functions for storing data](https://firebase.google.com/docs/firestore/quotas), but it is obvious that some problems with the size of it.

What I've done is editing `firebase.json`.

```json
"functions": {
  "source": ".",
  // ...
  "ignore": [
    "**/src/**",
    "**/.vscode/**",
    ".firebase/**",
    ".firebaserc",
    "firebase.json",
    "**/node_modules/**",
    "**/public/**",
    "**/.next/cache/**"
  ]
}
```

The cause I set the path of `source` as `.` is I need to deploy the `function.js` and `.next` folder simultaneously. But I've missed that it included `src`, `node_modules`, and `.next/cache`. These were completely unnecessary to me, so I add some lines on `ignore` option of `firebase.json`.

Then, the package lightened and the problem solved! Its size reduced to 30MBs and deployment is way much faster now. I strongly recommend trying this optimization with it.

### 403 occurred when access to hosting(From Firebase Functions)
And a few days later, I've got another error like, `Your client does not have permission to get URL (function name)`.
It was after deployed the new version of the app, it just returned 403 page, without any logs in Functions dashboard. Also, the backend using firebase functions worked fine and it made me super confusing.

I read carefully the response, and it was not the page that I've implemented,  which means it is not occurred by my codes. What I tried is:
- Delete and install latest `firebase-tools`
- Change the function name and deploy it as a new function
- Check the GCP -> Cloud Functions -> Authentication

As a result, the third one solved the problem. I'm not sure why, but `Authentication` setting for the function `nextServer` was changed to need the auth. This was the cause of the error.
After add `allUsers` as a Function Invoker (this project needed to keep published), it works just as I intended.

### Conclusion
So, that’s it for this article.
First of all, thanks to [Dipesh K.C.](https://dipesh-kc.medium.com/) who wrote [the article](https://medium.com/wesionary-team/deploying-next-js-application-on-firebase-platform-using-cloud-function-with-firebase-hosting-920157f03267) saved me a lot.
I Usually use Vercel or Netlify for personal projects, but this time, the client demanded to use Firebase for their reason.
Maybe, [using Google App Engine is the better way for some cases](https://medium.com/wesionary-team/deploy-your-next-js-application-on-google-app-engine-in-minutes-cf04c18011ac).
But it gave me a chance to understand Next.js and Firebase more deeply. Hope this article helps someone have the same trouble as me. Any suggestions and corrections are welcome.

https://github.com/rowaxl