What I struggled with Next.js using Firebase Hosting (and enable SSR)

rowaxl

Wonjae Kim

Posted on February 27, 2021

What I struggled with Next.js using Firebase Hosting (and enable SSR)

Overview

Based on this article:

Deploying Next.js Application on Firebase Platform using Cloud Function with Firebase Hosting. by Dipesh K.C.

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 helps me a lot.

Create a Next.js App

First of all, start with creating the boilerplate Next.js app.

$ yarn create next-app

or

$ npx create-next-app
Enter fullscreen mode Exit fullscreen mode

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

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 } }
}
Enter fullscreen mode Exit fullscreen mode

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.

Deploy Next.js App with only Hosting (SSG)

Ready Firebase Projects

At first, I used SSG, because Firebase Hosting supports only static files.

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
Enter fullscreen mode Exit fullscreen mode

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.

// 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 } }
}
Enter fullscreen mode Exit fullscreen mode

So, it will shows like below when it runs.

Alt Text

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

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

$ firebase login

// login page will show up.

$ firebase init

// select the hosting only at this time.
Enter fullscreen mode Exit fullscreen mode

Then, edit firebase.json a little bit.

{
  "hosting": {
    "public": "out",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

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

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

  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 } }
}
Enter fullscreen mode Exit fullscreen mode

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

Add Next server functions

// 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));
});
Enter fullscreen mode Exit fullscreen mode

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

And, update your firebase.json like below

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

Also, update package.json for deploy command

{
  "name": "firebase-deploy",
  "version": "0.1.0",
  "main": "function.js",
  // ...
}
Enter fullscreen mode Exit fullscreen mode

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

πŸŽ‰ 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:

<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>
Enter fullscreen mode Exit fullscreen mode

Initially, my Next app was over 200 MB after packaged. I believe Firebase Functions supports 1GB of functions for storing data, but it is obvious that some problems with the size of it.

What I've done is editing firebase.json.

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

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. who wrote the article 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.
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

πŸ’– πŸ’ͺ πŸ™… 🚩
rowaxl
Wonjae Kim

Posted on February 27, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related