Delivering Rendered React from Google Cloud Functions
My Nordstrom team is one of several groups comprising our overall engineering platform, which provides the technology foundation for delivering quality user experiences. Because we create internal tools to reduce friction and streamline processes for our developers — we write a lot of small, low-traffic web applications with third-party integrations.
For example, we might want to create an application that could automate a deployment to our Kubernetes cluster, or maybe configure rate limits in a load balancer. With low-traffic applications, we like to use a serverless approach, which helps us be efficient. We also like to use React, which poses a problem when integrating with third-party services.
In this article, I’ll walk you through creating a Hello World React application from scratch, and deploying it to Google Cloud Functions — and bundling in server-side rendering to boot!
React and Server-side Rendering
React is a popular library used by many for developing web apps, for collaborating with other teams and integrating third-party resources. Teams are likely to be able to grok a React app, and third-party services are likely to have integration examples written with React.
The opportunity lies in these third-party services: If we are going to use React, the simplest solution would be to create a single page app, host it from a content delivery network, and call it good. However, third-party integrations require authentication— API keys for accessing these outside services.
As an added bonus, this makes our application fully-visible to web crawlers — a small boon for an internal application like our, but a huge win for anything on the public internet.
Going Serverless: Google Cloud Functions
Where should we render this payload? As mentioned above, the React app has a limited scope and has low traffic, which means that a serverless architecture is a very compelling choice. Google only charges users for cloud functions as they are running — which can make this a cost-effective solution. Google’s pricing page has a number of examples of how to calculate this.
Let’s do it — Hello World!
After that’s completed, we can deploy to the cloud with just a few commands in our terminal and one trip to our text editor. First, let’s create our new project directory, and initialize a new Node project in it:
$ mkdir my-cloud-function-app
$ cd my-cloud-function-app
$ npm init -f
This gets our directory all set up with the Node framework. We use the -f flag here to skip some setup and streamline things.
Now that the project is initialized, we can create our first file. GCP is expecting our cloud functions to be exported from a file called
index.js. Open this in your favorite text editor and add the following content:
If you have some familiarity with web development in Node, this might look familiar to you — Google Cloud Functions are based off of the popular Express library. This snippet sends a little greeting to those who invoke the cloud function.
Since we’ll be deploying frequently, let’s add a deploy script to the “scripts” field in our
"test": "echo \"Error: no test specified\" && exit 1",
"deploy": "gcloud beta functions deploy render — runtime nodejs8 — trigger-http"
Now we can easily deploy to GCP:
$ npm run deploy
You can copy the url listed under
httpsTrigger to see your greeting:
Configure Local Development
It’s great that our code is working — but it’s pretty tedious to have to wait (up to 2 minutes!) to see the results of any changes we’ve made. To make this fast, we can set up a local Express server to emulate Google Cloud Functions’
$ npm install --save-dev express
Remember how I said that Google Cloud Functions are based off of Express? This makes it straightforward to host them locally.
All you need to do is create a
local/server.js file, and add something to it:
$ mkdir local
$ touch local/server.js
Let’s add the code below! Check out the comments to see what’s going on.
Now, to see our cloud function executing locally, just run:
$ node local/server.js
It’s great that we have our Express server doing a credible impression of Google Cloud Platform, but we have a few more things we need to get our local development situation in order.
First, let’s add some new dependencies:
$ npm install --save-dev webpack webpack-cli webpack-node-externals start-server-webpack-plugin @babel/core @babel/preset-env babel-loader
Let’s have a chat about these new dependencies:
- webpack will be performing double duty — bundling all our our files into one efficient file, and enabling Hot Module Replacement (HMR). HMR will allow us to make changes to our code and see those changes reflected immediately in our local server.
- webpack-cli lets us run webpack from scripts.
- webpack-node-externals makes sure that webpack omits all of the node libraries we depend on. This is considered best-practice for backend services.
- start-server-webpack-plugin allows webpack to start a server for us after doing her thing. This is a key library for enabling HMR in our local server.
- @babel/core handles all our transpilation (taking our code and rewriting it to be compatible with whatever standard we configure it for).
- @babel/preset-env sets defaults for babel that will allow us to use up to ECMAScript 2017 syntax without any additional configuration.
- babel-loader lets us use babel in webpack.
After installing, webpack requires some configuration to do its magic for us. In your project directory, create
Check out this helpful article on what exactly goes into a webpack config. The key parts that are making our HMR app work are setting
webpack/hot/poll?1000 as an entry and setting
watch: true. These let webpack know to reload any modules that have changed.
To get our app to refresh, we need to configure it to respond to the Hot Module Reload. Let’s add the following to bottom of
Start up your local server (
npm run local) and visit http://localhost:3000/render . Try changing the
index.js file to return a different message then refresh the page back in your web browser — Tada!
Step Two: Add React
We’re almost there! Now we’ll turn this from a regular old API into a full-fledged React App:
$ npm install --save react react-dom
$ npm install --save-dev @babel/preset-react
Let’s have another chat about each of our dependencies:
- react is the framework to build our front end.
- react-dom renders the React app for us, server-side.
- @babel/preset-react handles the configuration for the transpilation of all of our react code into a more portable syntax.
Now we can create our application code! Let’s first create the directories that React expects these files to live in:
$ mkdir App App/components
Let’s create a simple Hello World component in
We can use ES6 syntax here — thanks, Babel!
Let’s also create
We also need to update our Google Cloud Function in
index.js to respond with React rather than plain text. While we’re at it, we’ll also update to modern ECMAScript, now that we have Babel configured:
Important note: This uses the
renderToString method, which returns HTML ready for React to attach to. For a static web page, like this Hello World, that’s fine — but for an interactive app we’ll need to later
hydrate the DOM. This type of serving — render to string and hydrate — is sometimes called isomorphic rendering, and is the subject of the next blog post in the series!
Finally, we need to let webpack know that she should be expecting some React code! To do this, simply add
"@babel/react" to the
presets field in
npm run local and visit http://localhost:3000/render , and you should see something like:
Finally: Deploying React to GCP!
Now that we’re including a bunch more modules, we need to update our deployment script! Webpack will handle part of this — simplifying our app into one file that we can deploy to GCP. To get it to do this, create a
Looks much simpler than our local config, doesn’t it? We need to add this to our scripts in
package.json as well:
"test": "echo \"Error: no test specified\" && exit 1",
"deploy": "cd dist && gcloud beta functions deploy render --runtime nodejs8 --trigger-http",
"build": "webpack --config webpack.config.cloud.js && cp package.json dist",
"local": "rm -rf ./build && webpack --config webpack.config.local.js"
The new “build” script has webpack bundle up our app, then copy the
package.json file to the resulting directory. This lets npm do its thing when we deploy this to GCP. We also added a “deploy” script that runs from our
dist folder so it sends our bundled code to GCP. Try it out:
$ npm run build
$ npm run deploy
Just as before, you can copy the URL in the
httpsTrigger field to see your app run, live in the wild — and delivering fully-rendered HTML directly from the cloud!
As you can see, getting a basic static application running completely on the cloud — is a fairly simple process.
As mentioned above, there’s one more thing need to do to have a fully-interactive React application: Enable isomorphic rendering. I’ll be telling you just how to do this in a follow-up article!