Free, robust and collaborative create-react-app deployments on Netlify
I’ve been helping a startup bootstrap their frontend application infrastructure, and deployment has been swell with create-react-app
and Netlify. When a change is pushed to the master
branch, Netlify builds and deploys the app to production 🎉
The team at Facebook have really hit a home run with create-react-app for zero-config React applications. Similarly, the team at Netlify have made continuous delivery of static sites a breeze.
There were a few things left to be desired with this setup, though, especially as more people started collaborating on this project, and doubly so once customers started using it.
We needed to take this stack to the next level, and enable something like a delivery pipeline for individual features. I’m going to cover a few things we did to make development and release environments more robust and reliable in this post:
- Tagging deploys with build variables for better observability and error tracing
- Enabling preview deployments on pull requests, and dealing with complexities introduced by having multiple deployments
- Leveraging Netlify’s Continuous Delivery platform as Continuous Integration
Finally, this led to the creation of https://www.npmjs.com/package/build-create-react-app-netlify to help others benefit from this setup!
Tagging Netlify + create-react-app
deploys
For now, we have no reason to eject from the baked in create-react-app
setup. The zero-config nature provides everything we need. It’s really great!
By default create-react-app
only injects environment variables that are prefixed with REACT_APP_
into the build. Netlify’s build variables, understandably, are not prefixed in this way.
By creating a custom build-create-react-app-netlify
script, and configuring Netlify to run that instead of the default yarn build
, we can work around this:
#!/usr/bin/env bashset -eecho 'Mapping Netlify build env vars for create-react-app, and running build ...'set -xREACT_APP_REPOSITORY_URL="$REPOSITORY_URL" \
REACT_APP_BRANCH="$BRANCH" \
REACT_APP_PULL_REQUEST="$PULL_REQUEST" \
REACT_APP_HEAD="$HEAD" \
REACT_APP_COMMIT_REF="$COMMIT_REF" \
REACT_APP_CONTEXT="$CONTEXT" \
REACT_APP_URL="$URL" \
REACT_APP_DEPLOY_URL="$DEPLOY_URL" \
REACT_APP_DEPLOY_PRIME_URL="$DEPLOY_PRIME_URL" \
yarn build
Note: this has been published on npm as https://www.npmjs.com/package/build-create-react-app-netlify
The application’s instrumentation and error reporting immediately benefited from this change. For example, we now use these variables to log our errors with the git hash and branch that triggered that build, allowing us to reliably monitor and trace errors across multiple deployments.
See Netlify’s docs on build environment variables for more details on what each of these mean.
Preview Deployments and Complexity
Another goal in this process was to enable preview deployments on pull requests. Netlify makes this trivial, but can only offer so much in terms of managing the complexity of multiple deployments.
For example, this app uses Auth0 for authentication. Auth0 needs a callback URL to work, and we had no way of reliably knowing the current deployment’s URL.
Different deployments = different URLs, and we had no way of reliably knowing the current deployment’s URL.
Out of the box, this led to deployments that were unable to authenticate, so we couldn’t log in or use these preview apps in any meaningful way.
Once we implemented the build-create-react-app-netlify
script above, we made use of process.env.REACT_APP_DEPLOY_PRIME_URL
to set the dynamic callback URL, and to deploy usable previews from each pull request.
Note for Auth0 users: you’ll also need to add something like https://*--your-app-name.netlify.com
to your “Allowed Callback URLs” for this to work.
Configuring multiple deployments
We checked-in a custom netlify.toml
to declare context-specific environment variables and overrides for different types of deployments. I’m comfortable checking these in since environment variables get exposed in the public output of the build, and aren’t terribly secret for this static frontend.
It looks something like:
# Global settings applied to the whole site.[build]
publish = "build"
command = "./scripts/netlify-build"# PRODUCTION context: All deploys to the main
# repository branch will inherit these settings[context.production.environment]
REACT_APP_ENV_LABEL = "production"
REACT_APP_API_URL = "https://api.example.com"
REACT_APP_S3_BUCKET = "ex_production"
REACT_APP_AUTH0_CALLBACK_URL = "https://portal.example.com/callback"
REACT_APP_SOME_KEY = "abcd1234"# Deploy Preview context: All Deploy Previews
# will inherit these settings.[context.deploy-preview.environment]
REACT_APP_ENV_LABEL = "review"
REACT_APP_API_URL = "https://api-staging.example.com"
REACT_APP_S3_BUCKET = "ex_staging"# Branch Deploy context: All deploys that are not in
# an active Deploy Preview will inherit these settings[context.branch-deploy.environment]
REACT_APP_ENV_LABEL = "review"
REACT_APP_API_URL = "https://api-staging.example.com"
REACT_APP_S3_BUCKET = "ex_staging"# Specific branch context: Deploys from a branch
# will merge matching config into the preview
# or branch deploy config.[context."feat/testing-something-new".environment]
REACT_APP_NEW_VAR = "o"[context.staging.environment]
REACT_APP_ENV_LABEL = "staging"
REACT_APP_AUTH0_CALLBACK_URL = "https://portal-staging.example.com/callback"
See Netlify’s docs on Deploy Contexts for more detail on the netlify.toml
file.
We also use Netlify’s UI to declare global environment variables, or ones that we might want to change without touching code.
Netlify as Continuous Integration
We wanted to leverage the tests we had been writing to validate each build. Netlify is a fantastic continous delivery tool, but it was not clear if we could use it for continuous integration in this way.
Fortunately, Netlify’s infrastructure makes this mostly painless (more on this below). By adding another command to the top of the build-create-react-app-netlify
script, we run tests first and only proceed with a build if they are successful:
#!/usr/bin/env bashset -eecho 'Running tests ...'
yarn install --production=false && CI=true yarn test...
And with that, failing tests in a branch will be reported correctly and halt the deployment.
A few pain-points/notes — Netlify installs node dependencies in production mode. This means that any dev dependencies, including create-react-app
's dev dependencies are not installed. This happens to be where most test tooling is installed.
So, the first part of this command does another install, but includes all dependencies with the --production=false
flag. This allows us to run tests, but can add up to 1–2 minutes to the build.
Then, setting CI=true
for create-react-app
’s test command ensures that tests are run once and then exit. Without this, the test suite enters watch mode and will cause the build to time out.
Conclusion
I think this is an important infrastructure improvement that will help the team iterate and collaborate effectively on this project.
In the next few days, I hope to publish the build-create-react-app-netlify
script on npm, so that anyone can benefit from the same setup with ease.
Update: This is now available at https://www.npmjs.com/package/build-create-react-app-netlify
If you’re using Netlify and create-react-app
, I hope this helps you towards a more reliable and observable application. Feel free to reach out with questions or feedback. And if you’ve solved these problems in different ways, I’d love to hear more about it!