About a year ago, I used Remix on a short prototype app that displayed a series of reports, and I loved it. For those who don’t know. Remix is a framework for building websites. It uses React JS as a base and adds some specialized compiling. The app was a great use case for Remix for a few reasons.
The Case for Remix
First, the pre-built stacks got the app deployed quickly. The main parts of Remix that shined were its prefetching, directory-based routing, and loader functions. The prefetching helped the reports load faster because some data would be retrieved before the user navigated. The directory-based routing made it easy to set up the structure of the app. Lastly, Remix’s loader functions could retrieve small amounts of data each as it was needed. I am now using Remix on a second website that has a different structure than the prototype and we aren’t able to use the same features to the same degree.
This time around, I am finding myself using fewer of the features mentioned previously. We aren’t using prefetching because we have a single page with a lot of complexity. We have fewer loader functions retrieving lots of information at once which makes them perform worse. The main difference that I found between the two projects is that the prototype was a multipage app and the second has been turning into a single-page app (SPA). Based on Remix’s documentation and my experience, the best use cases for Remix are ones you can model as multiple pages.
Challenges for Single-Page Apps
For this post, when I refer to a single-page app I am referring to an app that tries to pack a variety of behaviors onto a single route. An example would be Google Calendar. Pictured below:
To render this page, we need to load a few different things. We need to load all the events for this month, all the calendars for the user, and the details for the apps along the lefthand side. Then to use the app, we need to track which event is selected. When the user drags an event, we need to keep track of the calendar it is on. Then when it is placed, an alert displays any conflicts. This state and behavior may need to be done in one component, which means one loader function. We may also want to perform component tests, which are only partially supported in Remix, based on this git conversation.
Instead, let’s talk about structuring an app to leverage Remix’s strengths. We want to make use of Remix’s speedy loading while getting around some of its component testing limitations. The files used for routing should each be responsible for retrieving, displaying, and mutating a limited set of information. As an example of a UI structure that could work well in Remix, let’s look at Slack.
Slack’s UI can be visually broken down into three sections. The workspaces are on the far left. Then the channels within the selected workspace are listed, and lastly, we have the current chat for the selected workspace.
A Remix app with this UI could have a URL like this: slack/workspace2/proj-new-website. The root of the app would get the list of all workspaces, and then /workspace2 would load the details for the selected workspace. Lastly, the chat window for /proj-new-website would load the chats and display them. You can model this app as a multipage app because the responsibilities are spread at different levels of the page instead of concentrated in one component, as with the Google Calendar example. This would be a Slack-like app and a better use case for Remix than a calendar app.
Using the Right Tool
Remix is a powerful tool for web development. It can produce polished prototypes quickly and build performant production apps. However, an app in Remix needs to keep Remix’s design principles in mind. This means splitting the app’s pages into pieces that can be loaded and rendered separately and keeping components as simple as possible to limit component testing on the frontend. Designing an app this way will enable the app to leverage the performance benefits promised by Remix.