Day 8: Building a CrossFit Stats app with React and Next.js

So there’s this thing called the CrossFit Games. Specifically, an event under that umbrella exists called the CrossFit Open.

Hundreds of thousands of people from around the world participate in the same series of five workouts during the month of February and March. It’s all in affiliation with their local CrossFit gym.

Each athlete’s stats gets logged by a certain date. There is a global leaderboard of athletes competing in the games.

The leaderboard

In case you were wondering, I clock in at about 130,000th place — too far down to show in this screenshot.

The leaderboard on the official website leaves a bit to be desired. Specifically:

  • I can see my stats by searching for my name within my division, but I have to scroll through 50 other entries
  • The expandable table interface is a bit limited and tough to consume
  • I don’t have a reference for the corresponding workout - only my score for that workout. It’s tough to remember which workout was which each year!

Doing my own thing

I noticed that the official leaderboard was powered by a JSON API. Sick. I saw this as an opportunity to build my own thing.

The goal was to combine an athlete’s performance with details from the workout.

I’m calling it: Openboard.


And guess what: I streamed most of it live on the Internet!

Part 1: The bones

My strategy for starting the app was:

  1. I knew I wanted to deploy the site on Zeit Now. They’re a really cool, modern serverless hosting company.
  2. I wanted to get better at React, as well as learn Next.js, which is a server-side rendered version of React (also created by Zeit)

To start, I copied some static, stubbed JSON data and extracted it into functions like fetchAthletePerformance(). This allowed by to quickly iterate over some sample data without having to make real network requests. It also made it simple to swap in a real JSON endpoint when I was ready to go live.

The video above involves me parsing through this sample JSON data and figuring out how to piece important details together like workout name, workout variation for a division, and loading metadata.

I was happy to get as much done in one hour as I had. The end result wasn’t pretty, design-wise — so my next chunk of work was the design part.

Part 2: Let’s put some makeup on this thing

This was a struggle. I am admittedly bad at design, but I’m trying to get better at it.

As I approached a strategy to building out the user interface for Openboard, I ended up going with styled-jsx for a CSS-in-JS framework. While I’ve used styled-components in the past, styled-jsx was built into Next.js out of the box (because, well… Zeit).

This felt a little weird!

<style jsx>{`
  .nav-bar {
    display: flex;
    justify-content: space-between;

But after a while, it felt like I was writing normal CSS. I only encountered a couple weird things, mainly related to attempting to style nested child elements generated by third-party components (like React Autocomplete). In that case, I stuck on the global attribute and used some descendant logic accordingly.

At this point, I also deployed this baby for the first time! It was a real breeze, using Zeit’s official Next.js example.

I really dig the concept of immutable deployments. It allows you to ship a unique version of your project at unique url, like It’s immutable, meaning it’s stuck like that forever in time. No need to modify anything - just ship a new version. When it’s ready, I run now alias (or just let GitHub auto-deployments take care of that when merging into master).

Part 3: Dark mode and fine-tuning

To be honest, migrating to a dark theme was easier than I thought. I’m not sure if it’s actually a better design, but I’ll take it.

Openboard detail page

It was really fun building a year navigation component and integrating directly with routing support in Next.js. Sadly, I ended up disabling this feature because — wouldn’t you know it — I discovered the CrossFit API results and structured inconsistently year-to-year.

This is not surprising. Honestly, I’m just glad the results are available via a modern API and not locked down in weird HTML views and Java applets.

As I built this last part, I hit a few bumps in the road.

Between Saturday and Sunday, TypeScript support was broken due to a merge in a package used by the Now builder. I used TypeScript for my proxy server to fetch and return API requests.

Broken TypeScript

But this wasn’t a huge deal - I was able to lock to a previous version and get going again.

Another thing I ran into was trying to determine whether I was in a local environment so I could route proxy API requests accordingly (to my development server or to the production server). I tried to use publicRuntimeConfig config variables, but that led to A) successful deploys but B) a broken website with vague error messages in the logs.

Thankfully, this support thread made it clear that — indeed — plugins or other config options are not yet supported using the serverless mode for Next.js. I should have checked the Github docs for the canary version of Next.js for more information instead of the Next.js Production Deployment docs.

I’m also pretty sure I never got the publicRuntimeConfig to work locally in the browser-executed scripts, either - which kind of defeats the purpose.

I ended up hardcoding the routes to point to production, because I wanted to share it with people 😀.

Honestly, I am blown away by how slick the Next.js framework is and by how easy it is to deploy to Zeit Now. The hiccups along the way are just the price you pay for using bleeding-edge technology.

Visit Openboard at! Search ‘josh larson’, or view my stats page here: