Server Side Rendered React on Google Cloud Functions

Ernesto F
Google Cloud - Community
5 min readMay 24, 2017

Rendering HTML is one of the use cases of Serverless I first tried. I like the idea of:

  1. A single function (Serverless)
  2. Rendering React components (Server Side Render)
  3. On an HTTP endpoint (Microservices composition)
  4. Returning full-fledge app/pages. (Single Page Apps)

The benefits of each one of the remarked comments are well documented online. From SSR providing better SEO and faster responses on mobile, to microservices defining most of the big companies infrastructures today, to ever complex SPA powering our favorite social network apps and collaboration tools online, to Serverless’s amazing potential for cost savings and infinite scalability.

Lets try to put together an simple example of how to achieve them all in a single SPA, written in React deployed on Google Cloud Functions.

The City

Serverless Function

Lets start with a simple Google Cloud Function.

$ mkdir my-serverless-app
$ cd my-serverless-app
$ npm init -y

Lets add some code at index.js:

And a new value in the script section of package.json:

"scripts": {
"deploy": "gcloud beta functions deploy my-serverless-app-1_0_0 --entry-point handler --trigger-http --stage-bucket medium-post-functions --memory=2048MB"
},

Once we have this couple pieces in place we can simply do:

$ yarn deploy

And watch our GCF being deployed. Notice I used 2048Mb memory. (feel free to change this later). At this point we have a HTTP endpoint returning static content.

Server Side Rendered React

Creating another implementation of SSR for React at this point in time is not very productive. There are a lot of good implementations of this already. Plus a good bunch of libraries to get up to speed with React development.

So, I will use Nextjs. The best way to learn how to use it is going to https://learnnextjs.com/ (I did it, took me ~20 mins to grasp the basics). Continuing on the same repo lets add Nextjs dependencies first.

$ yarn add react react-dom next
$ mkdir pages

And lets make an pages/index.js to serve as root of our app.

And a minor addition to package.json:

{
"scripts": {
"dev": "next"
}
}

So we can do now:

$ yarn devDONE  Compiled successfully in 3775ms                                                                                                                             > Ready on http://localhost:3000

Go to http://localhost:3000 and see our “App”

Browser frame art https://dribbble.com/shots/1582463-Browser-mockup

Nice, but not much for Server Side Render or Google Cloud Functions. We need to build our app and serve it using a handler function. The way Nextjs recommends to do this is by creating a Custom Server (https://learnnextjs.com/basics/server-side-support-for-clean-urls/create-a-custom-server)

Since our Custom Server will be only needed for GCF then we can simplify it to:

And also add one more script on package.json to build our App into a deployable variant.(by default built app is stored on .next folder, check it out)

{
"scripts": {
"build": "next build"
}
}

Finally:

$ yarn build
$ yarn deploy
....
httpsTrigger:
url: https://us-central1-revelatio-165320.cloudfunctions.net/my-serverless-app-1_0_0
....

We can see our endpoint rendering react app if we go to: https://us-central1-revelatio-165320.cloudfunctions.net/my-serverless-app-1_0_0/

Notice the trailing slash (/) at the end of our endpoint call. There seems to be a couple of errors from the GCF and Nextjs ends. GCF assumes the default route is an empty string, so

https://us-central1-revelatio-165320.cloudfunctions.net/my-serverless-app-1_0_0req.url === ''https://us-central1-revelatio-165320.cloudfunctions.net/my-serverless-app-1_0_0/req.url === '/'

And Nextjs fails if the path is empty. This little quirk breaks the endpoint without the trailing slash. A more robust version of our handler function could be:

After a redeploy we can safely access our endpoint HTTP without the trailing slash. Server Side Render FTW.

Microservices Composition

Everything fine from the SSR part. But, our App is fatally broken on the client side.

Failing resources showing on the browser console.

Our app resources are failing to be downloaded from the GCF. This resources includes basically all App assets, like Javascript bundles, CSS and Images (if any). Nextjs uses absolute routes to fetch assets and handle routing. We need to serve our App from a domain’s base root path or /.

A simple way to do this could be using NGINX:

http {
upstream app_server {
server us-central1-revelatio-165320.cloudfunctions.net;
}
server {
listen 80;
server_name www.mi-app.com;
location / {
proxy_pass https://app_server/my-serverless-app-1_0_0;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
}

NGINX is a good, GOOD piece of software. But, IMHO true Serverless approach should cost zero (cero) on server maintenance. So, lets choose another provider, in this case, Fastly (https://www.fastly.com/)

On Fastly we can create a Service:

  1. Set our own app domain.
  2. Set up Host to be our cloud function domain, including subdomain. (Ex: us-central1-revelatio-165320.cloudfunctions.net)
  3. Enable TLS and use port 443
  4. Certificate Hostname and SNI Hostname same value as #2
  5. Override host set same as #2
  6. Create a Header rule to transform the req.url property from the Request and rewrite all URLs from /* to /my-serverless-app-1_0_0/* (Type: Request/Regex, Destination: url, Source: req.url, Regex: ^(.*)$, Substitution: /my-serverless-app-1_0_0\1)

Of course Fastly demands you add a CNAME registry on your domain DNS to work properly.

Once all this settings are in place (took me 6 versions to got it right) You can have your app deployed on GCF routed trough a very performant cache and global CDN.

Other Google Cloud Functions, like, API backends, could be deployed using the same approach, on different routes of course. /api/v1 or /api/graphql.

(Check how to make a simple mongoDB backend on GCF https://medium.com/google-cloud/creating-a-mongodb-crud-backend-on-google-cloud-functions-88bb5c1cef77)

Single Page Apps

Well at this point this post is becoming too long. So, go https://learnnextjs.com and https://github.com/zeit/next.js/tree/v3-beta/examples and build great apps.

Edit 1: Changed a little bit the NGINX upstream example. I haven’t fully tested that approach, my recommendation would be for you to use Kong API Gateway (https://getkong.org).

--

--