Epic Web Conf late-bird tickets are available now, hurry!

Get your tickets here

Join the community and network with other great web devs.

Time's up. The sale is over

Why I Love Remix

November 13th, 2021 — 10 min read

blue and black compact disc
blue and black compact disc

kentcdodds.com is completely custom built by me (and team) using Remix. After writing tens of thousands of lines of code using this framework, I have developed a great appreciation for what this framework can do for me and the users of my site. I want to tell you about some of it.

In a sentence

Here's the core reason I love using Remix to build websites:

Remix enables me to build amazing user experiences and still be happy with the code I had to write to get there.

That's it. So what does that mean? Let's dig deeper...

The User Experience

There are a lot of things that impact the user's experience when they use our software. Most of the time I think people are focused on performance/speed and while that's one important aspect, it is only one part of things. The user experience includes a whole host of other aspects of your website though. Here are a few:

  • Accessibility
  • Performance
  • Content reflows
  • Reliability and availability
  • Error handling
  • Pending management
  • State management
  • Progressive enhancement
  • Resilience (behavior in poor network conditions)
  • Layout
  • Content clarity

Even the speed of development of features can impact the user's experience. So the user's experience is indirectly impacted by the maintainability of our code.

Remix helps with many of these things. Some without me having to think about it. In particular, some of the harder problems involving state management (race conditions of mutations and data loading) are completely managed within the framework. Because of this, users won't find themselves having to refresh the page because they're looking at stale data. This just happens without me thinking about it. It's just the way Remix works.

Remix does a lot to keep my website fast through its use of <link /> tags to preload assets and data at the right time. Sometimes I'm blown away by the fact that my site feels like it's static files on a CDN, but it's actually server rendered/hydrated and every page is completely unique to every user (so a shared HTTP cache on a CDN is not possible).

Remix's use of the platform APIs is what enables this. That's also what makes Remix so resilient and great for progressive enhancement as well. In poor network conditions where loading the JavaScript is slow/fails, Remix's standard API for mutations (<Form />) will actually work even before the app is hydrated. This means that the user can start getting work done with the app, even if they're on a poor connection. Much better than a completely unresponsive button whose onClick handler hasn't yet loaded (which was my own standard before Remix)!

Remix's declarative error handling means that it's easier for me to properly handle errors in the context of where the error happens. Combine this with nested routing and what you get is the ability to render a contextual error without breaking the rest of the app.

Excalidraw wireframe of a nested user interface with only one part that is broken and the rest is working

And also this works on the server as well (which is unique to Remix), so users will get the same experience whether the error happened during a client transition or a full document download.

Remix makes a terrific user experience the default. And that's one of the primary reasons I love Remix.

The Code

Apps I've helped build are used by millions of people all over the world. Building websites with Remix is the first time I can say that I'm truly happy with the code I deployed. The biggest reason for this is that before Remix I spent a lot of time just trying to deal with user experience issues. Because Remix helps so much with the user experience, I don't have so much complex code to manage myself. So all that's left for me to do is use the declarative APIs that Remix (and React) give me, to build my app and the user experience is good by default.

When I'm using Remix, I can leave my hacks at home.

Honestly that's the biggest thing I have to say about the code portion. You know how demo code is always so simple because it often skips the nuance of pending states, race conditions, error handling, accessibility, etc? Well, my code isn't quite like demo code, but Remix makes it feel pretty similar. I'm definitely still thinking about accessibility (though Remix's @reach-ui packages help a lot with that) and error/pending states. But the APIs that Remix gives me for those things are simple and declarative. I mean, here it is:

const loader: LoaderFunction = async ({request, params}) => {
  // this runs on the server
  // unexpected runtime errors will trigger the ErrorBoundary to be rendered
  // expected errors (like 401s, 404s, etc) will render the CatchBoundary
  // otherwise I can return a response and that'll render the default component
  return json(data)
}

export default function AttendeesRoute() {
  const data = useLoaderData()
  return <div>{/* render the data */}</div>
}

export function ErrorBoundary() {
  const error = useRouteError()
  // when true, this is what used to go to `CatchBoundary` in Remix v1
  if (isRouteErrorResponse(error)) {
    return <div>{/* render the error for 400-status level responses */}</div>
  }
  return <div>{/* render an "unexpected error" message */}</div>
}

Oh, and for pending states (whether mutations or regular transitions) you can stick this wherever you want the pending UI to show up (whether global or local):

const navigation = useNavigation()

const text =
  navigation.state === 'submitting'
    ? 'Saving...'
    : navigation.state === 'loading'
    ? 'Saved!'
    : 'Ready'

This drastically simplifies the React code that I write to the point that I don't write any HTTP-related React code. That client-server communication is completely managed by Remix and it's managed in a way that optimizes for the user's experience. Oh, and the client/server boundary can all be fully typed as well so I spend less time going back and forth between my browser and editor fixing silly mistakes.

I also love that Remix is founded on web APIs. That json function we're calling above in our loader? That's just a simple function to create a regular Response object. That's right. If you want to learn how to do something with Remix, you'll spend just as much (maybe more) time on mdn as you do on the remix docs and this brings me to another thing I love about Remix:

The better I get at building sites with Remix, the better I get at building sites for the web.

This happens naturally thanks to the fact that Remix uses so much of the web platform and defers to the web platform APIs as much as possible. (This is similar to how I feel that the better I get with React, the better I get at JavaScript.)

And because Remix is founded on the web APIs as the common interface for the server, you can deploy the same app to any platform (provided the code you bring along will run on those platforms) and all you have to do is change which adapter you're using. Whether you want to run on serverless or in a docker container, Remix has got you covered.

Remix is the jQuery of hosting platforms. It normalizes their differences so you can write once, host anywhere.

Another awesome part of the loader thing is that because it runs on the server I can hit APIs that give me far too much data and slim it down to just the part of the data I need. That means I can naturally eliminate the data overfetching problem that leads so many of us to reach for the complexity of graphql. I mean, you can still totally use graphql with Remix, but because Remix manages the client/server communication for you, you don't have to ship a huge and complex graphql client library to the browser and instead just rely on Remix to do the right thing at the right time (which it does).

And if I ever need more data than the server is sending to the client, I just scroll up in the file, change the loader to include the extra data I need, and I've got it right there. All typed and ready to go for my client-side code. It's fabulous.

I mentioned the <Form /> component earlier and how it will still work even before JavaScript loads. For that reason, it's great for the user experience. And it's also great for the developer experience, because I don't have to manage a bunch of fetch and state nonsense for my mutations. Normally, I have an onSubmit that adds an event.preventDefault(), fetch, race condition management, and cache invalidation code. Well, with Remix, all that goes away and I'm left with a declarative mutations API:

const action: ActionFunction = async ({request}) => {
  // this runs on the server and I can handle the request form data here
  // whether that be a direct database interaction or calling a downstream
  // service to perform the actual mutation. It's just brilliant.
  return redirect(/* send the user wherever you like after this */)
}

export default function AttendeesRoute() {
  // look mah! No event handler or useEffect necessary!
  // race conditions handled.
  return (
    <Form method="POST">
      <div>
        <label htmlFor="name-input">Name: </label>
        <input id="name-input" name="name" />
      </div>
      <div>
        <label htmlFor="email-input">Email: </label>
        <input id="email-input" name="email" type="email" />
      </div>
      <button type="submit">Add Attendee</button>
    </Form>
  )
}

Oh, and you want validation right? Well, you're gonna want to do that on the server, so you can put it in the action there. But you may also want it on the client right? Well, you literally just move the validation logic from the action into a function and call it in both the action as well as your component. That's it. Wowza.

Conclusion

I could go on and on. There are so many blog posts and workshops inside of me. I didn't even get to talk about how simple Optimistic UI is to implement with Remix, secure authentication, abstractability (code reuse), pagination, no <Layout /> components necessary thanks to nested routing, and so much more. I'll get to all that eventually I promise.

At the end of the day, it comes back to this:

I love Remix because it enables me to build amazing user experiences and still be happy with the code I had to write to get there.

And that's just something I can get behind and push on. Care to join me?

Epic React

Get Really Good at React

Illustration of a Rocket
Kent C. Dodds
Written by Kent C. Dodds

Kent C. Dodds is a JavaScript software engineer and teacher. Kent's taught hundreds of thousands of people how to make the world a better place with quality software development tools and practices. He lives with his wife and four kids in Utah.

Learn more about Kent

If you found this article helpful.

You will love these ones as well.