Skip to content

Introduction to Remix

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

What is Remix?

Remix is a full stack web framework based on web fundamentals and modern UX. It was created by the Remix.run team, founded by Ryan Florence and Michael Jackson. They are the creators of React Router.

Remix is a seamless server and browser runtime. It has no static site support, and always relies on a server. Remix aims to provide fast page load times and instant UI transitions.

Remix is built on the Web Fetch API, which allows it to run anywhere. It can be deployed in a serverless environment or in a Node.js server environment.

Remix features

Fast data fetching

Fetching data is so fast with Remix that there is no need for transitional spinners.

Rather than fetching data via a series of requests from components, Remix will load data in parallel on the server. It will then send the browser an HTML document that contains the data, ready to be displayed.

Making use of its own cache, Remix makes page reloads really fast. Remix will reload unchanged data from the cache, and only fetch new data.

Forms

Remix has a Form component, which is an enhanced HTML form component. Remix's Form component requires no onChange, onClick, or onSubmit events on the form or its fields. Contrary to traditional React forms, there is also no need for useState fields per form input.

Remix's Form component will automatically do a POST request to the current page route with all the form's data submitted. It can be configured to do PUT and DELETE requests as well. To handle requests from a form, a simple action method is needed.

Forms have traditionally been a point of frustration with React apps. Remix's approach to forms allows developers to create forms without having to write lines and lines of boilerplate code.

Routing

Similar to Next.js, Remix uses the file system to define page routes.

Remix is built on top of React Router v6. This means that all the React Router APIs can be used in a Remix application. Anything that works with React Router will work within Remix.

When navigating using a Link tag, the Outlet tag from React Router will automatically render the link's content on the page. This makes it easy to build a hierarchy of nested routes.

Nested routes

Nested routes allow Remix to make apps really fast. Remix will only load the nested routes that change. Remix will also only update the single nested component that was updated by some user interaction.

Nested Routes also provide nested CSS styling. This allows CSS to be loaded on a per page basis. When a user navigates away from a certain page, that page's stylesheet is removed.

Error handling

When a route in a Remix app throws an error in its action method, loader, or component, it will be caught automatically. Remix won't try to render the component. It will render the route's ErrorBoundary instead. An ErrorBoundary is a special component that handles runtime errors. It's almost like a configurable try/catch block.

If a given route has no ErrorBoundary, then the error that occured will bubble up to the routes above until it reaches the ErrorBoundary of the App component in the root.tsx file (assuming TypeScript is used).

Error handling is built into Remix to make it easier to do. If an error is thrown on the client side or on the server side, the error boundary will be displayed instead of the default component. This graceful degradation improves the user experience.

Project setup

Creating a new Remix project is as easy as using the following command in a terminal window.

npx create-remix@latest

You'll be asked to pick a folder name for your app. You'll also be asked where you want to deploy. Select Remix App server.

Remix can be deployed in several environments. The "Remix App Server" is a Node.js server based on Express. It's the simplest option to get up and running with Remix.

You'll then be asked to choose between TypeScript or JavaScript. The last step will ask you if you want to run npm install. Say yes.

Once installed, Remix can be run locally by using the following terminal commands.

cd [PROJECT_FOLDER_NAME]
npm run dev

Once Remix is running, you can go to localhost:3000 in your browser to see if the default Remix installation worked. You should see the following.

remix

Project structure

Let's explore the project structure that Remix created for us.

  • The public/ folder is for static assets such as images and fonts.
  • The app/ folder contains the Remix app's code.
  • The app/root.tsx file contains the root component for the app.
  • The app/entry.client.tsx file runs when the app loads in the browser. It hydrates React components.
  • The app/entry.server.tsx generates a HTTP response when rendering on the server. It will run when a request hits the server. Remix will handle loading the necessary data and we must handle the response. By default, this file is used to render the React app to a string/stream that is sent as a response to the client.
  • The app/routes/ folder is where route modules go. Remix uses the files in this folder to create the URL routes for the app based on the naming of these files.
  • The app/styles/ folder is where CSS goes. Remix supports route-based stylesheets. Stylesheets that are named after routes will be used for those routes. Nested routes can add their own stylesheets to the page. Remix automatically prefetches, loads, and unloads stylesheets based on the current route.
  • The remix.config.js file is used to set various configuration options for Remix.

Any file ending with .client.* or .server.* is only available on the client or the server.

Demos

The default installation of Remix comes with demos that allow us to see some of Remix's defining characteristics in action.

Forms and actions

Head over to http://localhost:3000 in your browser. Click on the Actions link under the Demos In This App heading. This will give you a chance to try out Remix forms and their corresponding action methods. To view the code for this demo, take a look at the app/routes/demos/actions.tsx file.

export default function ActionsDemo() {
   // route component
}

// action is called on the server for POST, PUT, PATCH, DELETE
export let action: ActionFunction = async ({ request }) => {
  let formData = await request.formData();
  let answer = formData.get("answer");

  if (typeof answer !== "string") {
    return json("Come on, at least try!", { status: 400 });
  }

  if (answer !== "egg") {
    return json(`Sorry, ${answer} is not right.`, { status: 400 });
  }

  // Redirect after a successful action 
  // prevents the reposting of data if the user clicks back
  return redirect("/demos/correct");
};

The action function above is a server-only function to handle data mutations. If a non-GET request is made to the page's route (POST, PUT, PATCH, DELETE), then the action function is called before the loader function. Actions are very similar to loaders. The only difference is when they are called.

CSS and Nested routes

Click on the Remix logo to go back to the welcome page. Now, click on Nested Routes, CSS loading/unloading to see how Remix allows certain CSS rules to only be included on specific routes and their children. To view the code for this demo, take a look at the files in theapp/routes/demos/about/ folder, as well as the app/styles/demos/about.css file for the route-based CSS.

Linking to a nested page is as simple as:

<Link to="whoa">A nested About route</Link>

Linking back to a parent route from a nested route is as simple as:

<Link to="..">Go back</Link>

Routing and Error Boundaries

Click on the Remix logo to go back to the welcome page. Now, click on URL Params and Error Boundaries to see how routing and error boundaries work in Remix.

To view the code for this demo, take a look at theapp/routes/demos/params/$id.tsx file.

Loader

This route defines a loader function that is called on the server before providing data to the route. When a route needs to fetch data from the server, Remix uses the loader function to handle that responsibility. The loader function aims to simplify the task of loading data into components. Contrary to Next.js, API routes are not needed to fetch data for route components in Remix.

Here is the loader function above the ParamDemo component.

export let loader: LoaderFunction = async ({ params }) => {
  if (params.id === "this-record-does-not-exist") {
    throw new Response("Not Found", { status: 404 });
  }

  // when the user just isn't authorized to see it.
  if (params.id === "shh-its-a-secret") {
    // json() is a Response helper for sending JSON responses
    throw json({ webmasterEmail: "hello@remix.run" }, { status: 401 });
  }

  // simulate an exception since lol() does not exist
  if (params.id === "kaboom") {
    lol();
  }

  // return the data to the component
  return { param: params.id };
};

export default function ParamDemo() {
  let data = useLoaderData();
  return (
    <h1>
      The param is <i style={{ color: "red" }}>{data.param}</i>
    </h1>
  );
}

The ParamDemo component uses a useLoaderData hook to get the data from the loader function. In this case, that data is the id value of the URL parameter that is passed to the route.

Error Boundary

export function ErrorBoundary({ error }: { error: Error }) {  
  return (
    <>
      <h2>Error!</h2>
      <p>{error.message}</p>
    </>
  );
}

An ErrorBoundary component is rendered when an error occurs anywhere on the route. Error boundaries are helpful for uncaught exceptions that we don't expect to happen. Clicking on the This one will throw an error link will trigger the ErrorBoundary.

Catch Boundary

export function CatchBoundary() {
  let caught = useCatch();

  let message: React.ReactNode;
  switch (caught.status) {
    case 401:
      message = (
        <p>
          Looks like you tried to visit a page that you do not have access to.
          Maybe ask the webmaster ({caught.data.webmasterEmail}) for access.
        </p>
      );
      break;
    case 404:
      message = (
        <p>Looks like you tried to visit a page that does not exist.</p>
      );
      break;
    default:
      message = (
        <p>
          There was a problem with your request!
          <br />
          {caught.status} {caught.statusText}
        </p>
      );
  }

  return (
    <>
      <h2>Oops!</h2>
      <p>{message}</p>
    </>
  );
}

A CatchBoundary component is rendered when the loader throws a Response. The status code of the response can be checked from within the CatchBoundary by using the useCatch hook. Clicking on the This will be a 404 and And this will be 401 Unauthorized links will trigger the CatchBoundary.

Conclusion

Remix is backed by some of the most talented engineers in the React community. Version 1.0 of this full stack React-based web framework was just released on November 22, 2021. It's now released under the MIT license and is open source, making it free to use.

For more Remix tutorials, check out the following:

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co