Hosting Next.js with Firebase
I use Firebase for some of my projects as the backend of some applications namely oneVcard and Clye. But now I need Server Side rendering for some of the routes to make Integrations and correct metadata handling as well as performance work the way it should.
To make it work I settled on Next.js. And there are a few options on how to make it work. If only
static rendering is needed a simple next export
is sufficient and the out
folder can simply be
deployed to Firebase. However that is not sufficient for my use case because I have dynamic content
depending on data in Firestore and potentially other places.
What are the options?
I want the process to be as automatic as possible and to scale based on demand without having to manually manage instances. Also A CDN for all the static parts is appreciated.
I need some way to execute the JavaScript code in the cloud. So first things to consider are the server less offerings. Those are listed in Googles Documentation.
Source:
cloud.google.com/serverless-options
According to this Diagram either Cloud Functions or App Engine standard environment are options. Maybe even Cloud Run is an option because It is Supported by Firebase Hosting.
Cloud Functions
The first thing I tried was to run the App as a Firebase Cloud Function. This worked but got
without further Optimization the deployment took a few minutes and Invocations take 13s for the
first invocation and speed up to just below 1s after that. That's not very amazing and definitely
to slow for my use. Another problem is that I used firebase Hosting and just redirected all request
to the cloud function. However I am in Europe and I could not find a way to redirect the traffic to
a cloud function that is located anywhere else then us-central1
. This is a pretty big problem
because all the other data is stored in datacenters located in Europe and all users are there too.
So this is not really an option for me.
Cloud Run
Cloud run seems to be a pretty compelling option. It uses docker containers to package everything that should run. Every instance supports up to 80 Connections, so it is not really efficient to hold the connection for example to send realtime updates and websocket is not supported. However that limitation is fine for my use case because it will only handle simple HTTP requests and Firestore or cloud-messaging can take care of the realtime aspect.
To test it out I followed the Instructions listed at cloud.google.com/run/docs/quickstarts/build.. .
Dockerfile
.dockerignore
node_modules
npm-debug.log
How to get access to Firebase services?
To start i just copied a firebase admin credential file into the docker container that I deploy. This is not a very gut solution, but good enough to get started.
Performance
local Node server
This is meant as a baseline. Any deployment will propably be slower.
Static
ab -n 100 -c 10 "http://localhost:3333/dashboard"
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.1 0 0
Processing: 4 8 1.1 8 10
Waiting: 3 7 1.0 7 9
Total: 4 8 1.1 8 10
Percentage of the requests served within a certain time (ms)
50% 8
66% 9
75% 9
80% 9
90% 10
95% 10
98% 10
99% 10
100% 10 (longest request)
Server generated
In this case it communicates with The database and then redirect and communicates with firestore again to generate the dynamic HTML.
ab -n 100 -c 10 "http://localhost:3333/c/test"
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.1 0 1
Processing: 40 60 24.8 52 187
Waiting: 40 60 24.6 51 186
Total: 41 60 24.8 52 187
Percentage of the requests served within a certain time (ms)
50% 52
66% 55
75% 57
80% 67
90% 101
95% 117
98% 132
99% 187
100% 187 (longest request)
Directly
I Call the cloudrun containers with their url given in the cloud console.
Static:
Connection Times (ms)
min mean[+/-sd] median max
Connect: 58 78 8.1 77 106
Processing: 64 89 9.7 90 112
Waiting: 38 50 6.3 49 68
Total: 122 167 13.8 168 202
Percentage of the requests served within a certain time (ms)
50% 168
66% 172
75% 174
80% 176
90% 186
95% 192
98% 201
99% 202
100% 202 (longest request)
Generated:
Connection Times (ms)
min mean[+/-sd] median max
Connect: 58 71 7.3 72 87
Processing: 50 74 21.1 69 180
Waiting: 50 73 21.2 69 180
Total: 112 145 24.1 139 259
Percentage of the requests served within a certain time (ms)
50% 139
66% 149
75% 157
80% 158
90% 173
95% 186
98% 255
99% 259
100% 259 (longest request)
This is expected and not to bad. It is over 100 ms
, but that is also true for the local
installation so not a real problem.
Firebase Hosting
Unfortunately not every location is supported but at
least locations that are pretty near are avaliable. For Next.js all static resources are served
under /_next/static
those should be cashed automatically by Firebase Hosting. Unfortunately that
did not work. But it is possible to enable it by setting the correct cache headers
firebase.google.com/docs/hosting/manage-cache. To do this with Next.js you can just modify
the next.config.js
file to inlcude the following lines in the configuration:
module.export = {
async headers() {
return [
{
// match all static files
source: "/_next/static/:path*",
headers: [
{
key: "cache-control",
// allow Firebase Hosting to cache it for up to one year
value: "public, max-age=31536000, immutable, s-maxage=31536000",
},
],
},
];
},
};
More documentation about headers can be found at nextjs.org/docs/api-reference/next.config.j...
Static:
Connection Times (ms)
min mean[+/-sd] median max
Connect: 46 54 7.2 51 78
Processing: 392 465 48.4 458 765
Waiting: 363 436 48.3 427 736
Total: 441 519 50.0 511 814
Percentage of the requests served within a certain time (ms)
50% 511
66% 528
75% 537
80% 542
90% 565
95% 596
98% 707
99% 814
100% 814 (longest request)
Server generated:
Connection Times (ms)
min mean[+/-sd] median max
Connect: 46 51 4.4 49 71
Processing: 445 4236 3265.8 3449 16136
Waiting: 445 4236 3265.9 3449 16136
Total: 502 4287 3265.4 3500 16194
Percentage of the requests served within a certain time (ms)
50% 3500
66% 4841
75% 5325
80% 6128
90% 8297
95% 12776
98% 14183
99% 16194
100% 16194 (longest request)
That is not acceptable, it takes up to 16 s for the page to load and normally it takes over 4 s which is unacceptable. That is far to slow.
App Engine standard environment
This runtime supports Node.js which is exactly what I need. There are also other runtimes available for Python, Java, PHP, Ruby and Go. But no Integration with Firebase Hosting exists which makes it harder to configure a CDN and to integrate with the rest of Firebase.
Solution
I will route my traffic directly to cloudrun and use Firebase Hosting as a CDN for static Assets. This way I should be able to avoid the overhead introduced by Firebase Hosting and at the same time I get most benefits of a CDN.