Remix - An overview of the framework. Traditional web standards for client-side developers

“I’m bringing proxy back.”

I’ve been a web developer for the last 6 years and I recently discovered Remix. I love what the framework can do and how it compares to Next.js.

I wanted to create a post about the differences between Next.js and Remix, but researching Remix, I decided it deserved a post of its own.

1. Introduction

Remix is a pretty new framework. It was created by Ryan Florence and Michael Jackson - the creators of react-router-dom, and later joined by Kent C. Dodds (our favorite React teacher).

The framework was initially released to a closed beta costing 250$ for a license. The team eventually changed their business model and released it free for everyone to use.

2. Philosophy

Remix is an edge-first framework built with end-user and developer experience.

Frameworks and libraries in the JS ecosystem have a big learning curve, and switching from one to another involves very little transferable knowledge. This leads to developers almost starting over when they change jobs.

The founders aimed to fix this by building Remix on standard Web API Standards.

While learning Remix, you’ll spend more time in the MDN docs than our docs — Ryan Florence

3. Nested routes

Nested routes are a misleading term. When I first heard it, I thought it meant my/very/deeply/nested/:dynamic-id/route.

And my reaction was, “So what? Other frameworks do this too!”

But in Remix, nested routes refer to splitting the layout of a single page into “paths. Here is an excellent explanation in the docs.

Here’s the talk on nested routes from the author himself at Reactathon 2022

4. Mutating Data

Data mutation is strictly done through forms, which for us client-side developers this is a foreign concept.

export const action: ActionFunction = async ({request}) => {
	const form = await request.formData()
	const note = form.get("note")

	const note = await db.note.create({data: {node}})

  return redirect(`/notes/${note.id}`)
}

export default function NewNoteRoute() {
	return (
    <div>
      <p>Add your own note</p>
      <form method="post">
        <div>
          <label>
            Note:{' '}
            <input
              type="text"
              name="note"
            />
          </label>
        </div>
          <button type="submit" className="button">
            Add
          </button>
        </div>
      </form>
    </div>}

On every (nested) route, you export an action function that handles all the form submissions from that route. You can create and delete in the same way with a hidden input with a _method value for delete and create

What’s remarkable is that if you send a form multiple times, it will be interrupted, and only the last one will be shown (Browser Api + Remix awesomeness):

Interrupting multiple form submissions. Credit Ryan Florence

You can share validation code between client & server; all the data in the forms are kept in sync by the server, so you don’t have to worry about users leaving because all the inserted data is lost.

5. Error handling

Remix recommends the concept of a happy path. Your UI function should only be concerned with that.

The framework provides a ErrorBoundary and a CatchBoundary component for handling all the cases where something went wrong. Example:

// Let's say only logged in users can create notes

// Handling of the form
export const action: ActionFunction = async ({request}) => {
	const user = getUser(request)
  if (!user) {
    // Yes! Throw the response to activate the Catch context
		throw new Response("You must be logged in, bro!", {status: 401})
  }
  const form = await request.formData()

  // creating a note
}

// The thrown Response is caught here
export function CatchBoundary() {
  const caught = useCatch()

  switch (caught.status) {
    case 401: {
      return (
        <div className="error-container">
          Sorry, you must be logged in to create jokes.
        </div>
      )
    }
    default: {
      throw new Error(`Unhandled error: ${caught.status}`)
    }
  }
}

// Boundary for errors happening while loading the page
export function ErrorBoundary() {
  return (
    <div className="error-container">{`There was an error loading this form. Sorry.`}</div>
  )
}

// Only cares about the happy path and is not concerned with user validation
export default function NewNoteRoute() {
	return (
    <div>
      <p>Add your own note</p>
      <form method="post">
        <div>
          <label>
            Note:{' '}
            <input type="text" name="note" />
          </label>
        </div>
          <button type="submit" className="button">
            Add
          </button>
        </div>
      </form>
    </div>}

6. Pre-fetching pages

Remix pre-fetches pages with a <Link prefetch="intent" /> which will fetch a page whenever the link is hovered.

You can fetch a page when the <Link /> to that page has been rendered using the prefetch="render" prop.

The team announced that more advanced pre-fetching based on user intent will soon come out.

7. Caching

Remix uses the Browser cache. That’s it. Everything is done with HTTP caching headers and nothing more.

But what about cache misses, bro?

If you don’t have enough users to warm your cache, you don’t have a performance problem, you have a marketing problem.

The caching works as follows:

Loader ⇒ Returns a page rendered on the server ⇒ After the first load, the page is cached

Action ⇒ Posted from a form ⇒ When an action is triggered, the cache for that resource is invalidated

9. Deployment

Remix was created to not vendor lock you, and so you can deploy anywhere you want, from Google Cloud, AWS, Fly, your father’s garage PC, and even their competition, Vercel.

The team recommends using Fly.io, and the deployment process was pretty simple.

6. Remix stacks

Remix stacks are pre-made JS tech stacks with deployment and scale in mind adapted to your needs with a music twist.

You get node servers (or lambdas) running on “the edge” (node containers on Fly.io or AWS)

Options:

  • The Grunge Stack — For you AWS rebels: Deployed to serverless with DynamoDB for persistence
  • The Indie Stack — For indie hackers listening to NPR: Deployed on Node.js servers on Fly.io with SQLite DB
  • The Blues Stack — For old school corporate enthusiasts: Node.js servers on FLy.io with pgsql DB (oriented for large scale apps)

More than classic solutions, the stacks are opinionated tech choices for people that want to get to work.

For most devs, the choices are modern and reasonable (RIP Jest); however, Remix also supports custom stacks — for people that hate ORMs like Prisma or Tailwind HTML

10. Community

The discord server is very active, and there are dedicated developers to help support new onboarded users.

The community is very dedicated. They are very loyal because the founders are very prominent and trustful people in the JS/React community.

The discord server

11. Future & Funding

The company secured a $3M seed round in early October last year, so we know they have some leeway for at least a year.

I’m confident you can safely bet your following company website on the framework as the community support is excellent, and the founders are prolific at web development. As a bonus, they love this product.

I assume that the plan for the business model is to create a cloud with first-class support for Remix deployment similar to Vercel. We will have to see

Whatever the plan is, more great features are coming in the future, and we’ll benefit from them.