The National Football League adopted React in December of 2014. For the past year we have iterated and built on React’s core concepts, various Flux implementations, JSX, Babel, experimental proposals, and functional approaches. You name it and we’ve probably evaluated it and possibly use it in production. Read on for a review of the NFL’s year in React.
We are big believers in React. To that end, the NFL Engineering team is more than eager to help increase visibility and adoption. We spoke at meet-ups and even hosted one ourselves. We also contributed to the open source community with tools like React Helmet (a document head manager), and React Metrics (an analytics helper).
The Path to Wildcat
From the ground up, we wanted to rethink our front-end development stack around React. We started with the traditional (if you can call year-old approaches “traditional”) React + Sass + Webpack bundle approach. We developed an internal React stack we called Gridiron and used it to launch our initial set of applications. Each application lived in a separate repo, had its own deployment pipeline, and ultimately ran its own application server in production. This approach very quickly revealed technical limitations and concerns:
- Dependency management across many repositories proved repetitive, time-consuming, and error prone.
- Updating shared components across many applications became tedious and unsynchronized.
- Webpack compilation negatively impacted the development experience by lengthening build, test, and deploy times as our codebases increased.
- Code-sharing suffered across repositories (especially as various Flux patterns emerged)
As the evidence grew, we began working on an in-house solution to address the following questions:
- How do we achieve continuous deployment?
- Can we be smarter about loading components on the browser?
- Can we effectively share React components across our properties?
- With HTTP2 on the horizon, can we practically adopt a bundle-free environment?
- Can we pre-render and serve React pages on multiple domains from one web application?
- Can we achieve our goals using a monolithic repository and server application across multiple domains and subdomains?
Thus began the path to Wildcat, an opinionated React environment we’ve developed and open sourced to address our concerns.
Wildcat runs behind-the-scenes. It is a pre-configured environment that handles server-side rendering and logic, leaving front end engineers to focus strictly on the client. It utilizes pragmatic, peer-reviewed dependencies that cover state management, routing, styling and dev tooling. Backed by Wildcat, work can immediately start on individual projects with no overhead distractions on libraries or frameworks. It consists of the following core packages:
- react-wildcat is the application server
- react-wildcat-handoff is the application client / server renderer
- react-wildcat-prefetch is an agnostic higher order component to prefetch component data
- react-wildcat-hot-reloader adds hot-reloading capabilities to Wildcat
- react-wildcat-ensure is a wrapper for System.import that behaves like Webpack’s require.ensure
- react-wildcat-test-runners manages end-to-end and unit testing with Karma, Protractor and Mocha.
Our framework attempts to solve a litany of architectural concerns, outlined across four core verticals:
The Application Server
Wildcat uses jspm and SystemJS to universally render React on the server and client. Using host headers, it resolves a user’s current domain and/or subdomain, lazily loading only what is necessary to render a given route. This technique allows us to maintain a monolithic repository, giving us the advantage of developing, running, and deploying multiple projects hosted across multiple domains based on a single application entry point.
Additionally, the app server handles data dehydration, prepping API data for client ingestion. It’s also responsible for ancillary tasks like request proxying, CORS handling and so forth.
In development, a WebSocket listens for static asset changes and hot-reloads modules as they change. You may be surprised to learn we also hot-reload in production. Using Kafka, our deploy pipeline publishes a notification of every static file change. The app server uses a Kafka consumer to listen for file invalidations and purges assets as they stream in. This allows us to publish static deploys without the need to bounce our app server. The server simply picks up the invalidation notifications, updates its in-memory module cache, and renders the updated module on the next reload.
Static Asset Management
One of our biggest challenges, static asset management was an architectural puzzle we debated for weeks. Prior to Wildcat, our decentralized deploy workflow revolved around minified, concatenated Webpack bundles. For each domain/subdomain, we generated a static asset bundle and served it from the target domain. This resulted in a high probabilty that modules would be duplicated across our domains. That is, if two separate domains depended on an identical module, that module would be downloaded once per domain. To put it in visual terms:
www.nfl.com -> moduleA -> www.nfl.com/bundleContainingModuleA.js
sso.nfl.com -> moduleA -> sso.nfl.com/bundleContainingModuleA.js
To solve this, we aimed to have a single source of truth for each shared dependency. We wanted the flexibility to deploy an individual module to one location and have it propagate across all our web applications. In Wildcat, we achieve this using a custom jspm + SystemJS fork that allows us to fetch modules on both the client and the server from a central destination:
www.nfl.com -> moduleA -> static.nfl.com/moduleA.js
sso.nfl.com -> moduleA -> static.nfl.com/moduleA.js
On the DX front a local static dev server listens for incoming module requests, locates the requested source ES2016 module, transpiles the module and serves an ES5 version back for client/server ingestion. One major advantage of on-the-fly transpilation is a lightning-quick dev mode boot time.
In production, source modules are transpiled on deploy and served as static ES5 modules through a CDN provider. Third-party modules are gathered, minified and bundled. Third-party modules shared across two or more domains are grouped into a common bundle, while domain-specific bundles are generated and served as needed.
- Route-based lazy component loading with React Router + jspm
- Radium for inline styling
- Helmet for managing your document head
- React Metrics for tracking analytics
- Store-agnostic Prefetching for client data hydration
In development, a WebSocket listens for static asset changes and hot-reloads modules as they change. Production changes are not hot-reloaded on the client.
As important as static asset management is, we also took the opportunity to improve our dev tooling with Wildcat. To ensure code quality, it is critically important to cover our code with unit tests, integration tests, and code coverage analysis. Our testing stack consists of Karma for unit tests and Protractor for e2e testing (turns out it works great for React!). Mocha+ Chai are used in both. We also use ESLint for static code analysis to catch potential bugs and to keep a uniform coding style across our web projects.
The Road Ahead
Wildcat is still in its infancy. It treads new, unexplored ground in many areas. We hope it can be used as a performance yardstick for the near-future of React applications. There are still many questions to answer and many features to refactor. We invite you to take a look at our framework and reach out with questions or concerns. The NFL Engineering team is only getting started with React. We can’t wait to see what’s next.