Glenn Viroux
Posted on May 5, 2023
Next.js is a powerful framework for building web applications, built on top of React.js and Node.js. As per Stackoverflow’s 2022 Developer Survey, React.js is the second most loved web framework, right after Node.js. Next.js provides a great set of features for building production-ready applications, such as server-side rendering and static site generation.
Firebase, on the other hand, is an easy-to-use platform by Google that helps you build mobile and web applications.
If you're developing a web application using these two technologies, you're in good company. However, when I was developing Ballistic, I stumbled upon a number of issues when trying to deploy the application. In this article, we'll explore these issues and walk through a step-by-step guide to successfully deploy your Next.js application on GCP.
Firebase Framework-Aware Hosting for Next.js
Firebase Hosting is a robust and reliable option for hosting websites, particularly when you require rapid and easy deployment. It is a fully-managed hosting service provided by Google, which allows developers to deploy web applications and static content to a global content delivery network (CDN) with a simple command. Firebase Hosting's free tier is particularly appealing for small projects, as it provides a limited amount of storage and data transfer per day at no cost. If your website grows beyond the free tier limits, you can use pay-as-you-go pricing, which allows you to pay only for the resources that you use.
Firebase's framework-aware hosting is a valuable integration that allows for seamless deployment of popular web frameworks such as Angular and Next.js. If you're using Next.js, Firebase will automatically detect your next.config.js
file and recognize that you're running a Next.js web application. This results in Firebase translating your Next.js settings into Firebase settings with minimal configuration needed. Additionally, if your app includes dynamic server-side logic, the integration will translate it into Cloud Functions for Firebase. This integration can significantly streamline the deployment process for developers, making it a convenient and efficient option for hosting web applications.
One of the challenges with these "black box integrations" is that when something goes wrong, it can be difficult to troubleshoot. In the case of Next.js and Firebase hosting, an error message such as "Couldn't find a pages directory. Please create one under the project root
" can leave developers scratching their heads. When you've verified that the issue isn't caused by your own code, you may be left with no other option than to create an issue on GitHub (if it's an open source project) or contact support and hope for a quick resolution.
That’s why, in this blog post, we’ll explore how to manually translate the Next.js configuration settings to Firebase settings, so we don’t have to rely on the experimental integration of Next.js in Firebase.
Translating Your Next.js App to Firebase
One of the core advantages of Next.js is its support for Server-Side Rendering (SSR), which allows web pages to be pre-rendered on the server, rather than relying on client-side JavaScript to render the page. This translates to a significant improvement in user experience, as the user can view a fully rendered HTML page almost instantly, without waiting for JavaScript or CSS files to load.
By translating your Next.js app to Firebase, you can further enhance its speed and scalability, as Firebase offers a robust and reliable platform for hosting and serving web content. With Firebase Hosting, you can easily deploy and serve your pre-rendered Next.js app to a global audience, providing them with a fast and seamless browsing experience.
To achieve this, we need to divide our Next.js codebase into two parts: the client and the server. The server part will define a Firebase function that handles SSR, while the client part will contain the rest of our Next.js codebase. In the following steps, we'll cover all the details of creating a Next.js project and deploying it to Firebase, from start to finish.
Setting up the Project
Let's begin by setting up our Next.js project by running the following command:
yarn create next-app next-firebase --typescript --eslint --src-dir --no-tailwind
Accept the default interactive options, and you should see a message saying Success! Created next-firebase
.
This will give you a fully functional Next.js codebase that you can test quickly by starting the development server:
yarn dev
Then, open your browser and navigate to http://localhost:3000
Creating Server and Client Separation
After successfully setting up your project in the previous step, let's move on to splitting your codebase into a server function responsible for SSR and a client codebase containing all the Next.js code.
Create a client
and server
folder inside the src
directory. Move the original contents of the src
directory to the client
directory, along with the public
folder, next.config.js
file, and tsconfig.json
file. This will leave the server
folder empty for now. We'll populate it with a Firebase function responsible for SSR in the next steps.
Creating the SSR Server Code
To create the function responsible for server-side rendering, we need to install some dependencies first. Run the following command to install them:
yarn add firebase-admin firebase-functionscd
yarn add **@babel/core @babel/cli @babel/preset-env cross-env rimraf**
After installing the dependencies, create an index.js
file inside the server
folder, as shown below:
import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';
import next from 'next';
admin.initializeApp();
const dev = process.env.NODE_ENV !== 'production';
const app = next({
dev,
// the absolute directory from the package.json file that initialises this module
// IE: the absolute path from the root of the Cloud Function
conf: { distDir: 'dist/client' },
});
const handle = app.getRequestHandler();
export const nextjsServer = functions.https.onRequest((request, response) => {
// log the page.js file or resource being requested
console.log('File: ' + request.originalUrl);
return app.prepare().then(() => handle(request, response));
});
Since we'll be transpiling this code with Babel, we need to instruct Babel on how to compile the code. We'll create a .babelrc
file inside the server
folder for this purpose:
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "10.15.3"
}
}
]
]
}
Updating package.json
We need to update the scripts in our package.json
to reflect the new folder structure we created:
"scripts": {
"dev:client": "next src/client",
"dev:server": "babel src/server --out-dir dist/server --source-maps --watch",
"dev": "yarn run dev:client & yarn run dev:server",
"build:client": "next build src/client",
"build:server": "babel src/server --out-dir dist/server --source-maps",
"build": "yarn run build:client && yarn run build:server"
}
At this point, you can spin up a local development server that hosts your client side code and serves your SSR server. You can also build a production-ready version of your Next.js app.
Also add the following lines to your package.json
, in order to override the main entrypoint to your server:
With these changes, you can spin up a local development server that hosts your client-side code and serves your SSR server. You can also build a production-ready version of your Next.js app.
Additionally, add the following lines to your package.json
to override the main entry point to your server:
"main": "dist/server/index.js",
"engines": {
"node": "16"
},
These lines ensure that when you deploy your app to Firebase, it uses the compiled version of your server code instead of the original source code. They also specify that the app requires Node.js version 16 to run.
Setting up Firebase
To integrate Firebase with our Next.js app, we'll need to create a new Firebase project in the Firebase console. If you already have a project set up, you can skip this step. Once you've created your project, you can add Firebase to your web app by navigating to the "Project Settings" page, clicking on the gear icon next to "Project Overview," and selecting "Add to Web App" on the "General" tab. Here, you can register your new web application.
Next, in the root directory of your project, run the following command to initialize Firebase:
firebase init
In the root directory of your project. During Project Setup, select the following options:
-
Firebase Hosting
andFirebase Functions
- use an existing project, and select the firebase project you just created or that you wish to use
- What language would you like to use to write Cloud Functions? → JavaScript
- Do you want to install dependencies with npm now? → No
- Detected an existing Next.js codebase in the current directory, should we use this? → No
- Do you want to use a web framework? (experimental) → No
- What do you want to use as your public directory? → Public
- Configure as a single-page app (rewrite all urls to /index.html)? → No
- Set up automatic builds and deploys with GitHub? → No
After running this command, a .firebaserc
file and a firebase.json
file will be created in your root folder. You'll need to modify the firebase.json
file to include the following code:
{
"hosting": {
"public": "public",
"rewrites": [
{
"source": "**/**",
"function": "nextjsServer"
}
]
},
"functions": {
"runtime": "nodejs16"
"source": ".",
"ignore": [
".firebase/**",
".firebaserc",
"firebase.json",
"**/node_modules/**",
"**/public/**",
"**/cache/**"
]
}
}
The previous process also generates a public
folder with a 404.html
file and an index.html
file. Make sure to delete the index.html
file before continuing.
Test your website and deploy
At this point, you can test your web app locally, share your changes with others, and deploy the app to the public. To test your app locally, you can use the serve
script from package.json
:
yarn serve
After verifying that your web app works fine locally by running the serve
script from package.json
, you're ready to deploy it to the public. To do this, you need to add the necessary deployment scripts to your package.json
file:
"scripts": {
"predeploy": "rimraf dist/ && yarn run build",
"deploy": "cross-env NODE_ENV=production firebase deploy --only functions,hosting",
}
Now, when you're ready to deploy your app, you can simply run yarn deploy
to deploy your app to Firebase hosting.
Conclusion
In conclusion, we have covered the entire process of building and deploying a Next.js web app with Firebase Cloud Functions and Firebase Hosting. By following the steps outlined in this post, you should now be able to create and launch your own Next.js app with Firebase.
The full source code for this project is available on GitHub at: https://github.com/GlennViroux/next-firebase-blog.
Thank you for reading, and I hope this article has been informative and helpful for you.
Posted on May 5, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.