DEV Community

Cover image for I tried Remix and it is too good to be true
Elisabeth Leonhardt
Elisabeth Leonhardt

Posted on • Edited on

I tried Remix and it is too good to be true

Spanish Translation/Traducción al Español

I struggle to finish user stories.
Before any PR or before assigning the story to QA, I open all of the code again. I read it line by line. Especially the parts where I had to compromise, where I wasn’t happy about how I did things, but I also didn’t know how to improve. I think: “If I just knew a little more, if I just had more experience, my code could be more performant, more elegant, or more readable…”
Of course, I submit the PR, and the tasks are closed on time. However, a part in my head doesn’t want to accept the “Done” and it saves the problem somewhere in a dark corner.
And after weeks, or sometimes months, I am reading an article, watching a video, or listening to an explanation when suddenly my head goes: “Now I know!”
I can’t always go back to the code and improve it, but I discovered a new way of solving a problem, which makes me a better programmer.
When I started learning about Remix, I had been using Next.js for almost 2 years. I also have experience with Gatsby. While reading the Remix docs, I had so many “Now I know!” experiences that I felt the need to share them. Like a woodworker needs specific tools to build furniture, as programmers, we need to know patterns, techniques, libraries, and frameworks so our code is easily scalable, maintainable, and can stand the test of time.
In the following sections, I want to go over my investigation process and why I believe that Remix is a valuable addition to my toolbox.

Phase 1: Collecting Information

To understand why Remix is a good tool to have in mind for a future project, it’s important to understand the alternative technologies with their advantages and disadvantages. Here is a quick summary of my own experience mixed with what I learned about Remix:

Advantages and Disadvantages of SSG

Static websites are the perfect use case for server-side generation. In the React world, a very common tool for developing static sites is Gatsby. After developing the site, it can be built and deployed, for example with services like Netlify, taking advantage of the CDN and thus reducing latency for the end-user. Problems start to arise if we want to do small changes, like fixing typos or adding a new banner to the home page. After the modification in the code, the whole page has to be rebuilt and redeployed.
Depending on the sites complexity and the amount of media that needs optimizing, rebuilding plus redeploying can take a considerable amount of time: Kent C. Dodds talks about this issue in his presentation about Remix: His built times were so long that they produced a timeout in Netlify.
To solve the mentioned problems, incremental static generation (ISR) was developed. This technology allows us to only rebuild the pages that changed. But if the required change affects a component present in all blog articles, its advantages can be diminished.
It is also possible to add dynamic data to static sites, but they have to be fetched on the client side. Static content has to be downloaded, javascript has to be executed, and dynamic content has to be fetched and then inserted into the DOM. This can take some time and therefore cause unexpected visual jumps on the site, also called cumulative layout shift (CLS). Unless a placeholder like a spinner or a skeleton is shown until the content appears, this can cause a bad user experience and can lead to your page failing core web vitals.

Advantages and Disadvantages of SSR

When a site has so much dynamic content that it is not feasible anymore to implement a statically generated site, server-side rendering is the answer. Upon request, the server generates the rendered content and sends it over to the client, thus making it possible to change content constantly. In the React World, Next.js is a framework frequently used in that case.
Since content is already rendered on the server side, cumulative layout shift is also taken care of. But we lose the advantages of the CDN and loading times can become an issue depending on a variety of factors such as server response times and latency between server and client.

Remix: the best of both worlds

If we don’t want to make compromises between the two technologies, Remix can be a good option because of the following:
With Remix, you can only build SSR applications, but it is built to take advantage of distributed infrastructure as a service like Cloudflare Workers or Fly, so you host your app as close to the user as possible.
Remix discourages client-side data fetching, which eliminates the need to ship libraries such as SWR, tanstack-query, or apollo to the browser. This of course implies a smaller bundle size and lots of saved mobile data for users who browse with their phones.
Since Remix pages are server-side rendered, layout shifts are taken care of.
I found several more aspects where Remix could introduce a performance benefit:
Some sites just become interactive when their javascript finished loading. This can confuse the user in the first seconds on the page if he tries to type into an input that is not responding. It is Remix’s philosophy to progressively enhance the site with javascript, not enable the site with javascript. This implies that inputs, menus, and buttons are immediately responsive, without depending on javascript.
Compared to Next.js, Remix can prefetch dynamic sites, for example, a result page while the user types in a search, thus making results appear almost instantaneously. This is possible thanks to Remix being a compiler, a server framework, and a client framework, therefore Remix can prepare the required components and fetch the necessary data while the user is still typing.

Phase 2: Try it out

With all that in mind, I started to dig deeper into the documentation and I created a repository to put the concepts into practice.

You can find the repository with all the code examples here

For a better learning experience, Remix provides a tutorial that guides you through the process of building a full-stack application from scratch with Remix. With that, I was able to play around with the following concepts:

  • File-based nested routing: With the help of the React Router documentation and the Remix tutorial, one part of my app was built just to play around with the concept of nested routing that Remix introduces. It facilitates layout composition: if your whole page needs a header and a footer and an administrative part of your page also needs a list of companies in a side-menu, Remix allows adding a nested layout for the /invoices domain with ease.

Nested Routing ilustration

  • (Kind of) Modular CSS and Tailwind: In Remix, stylesheets directly inserted into the HTML as link tags and CSS-in-JS solutions are discouraged. This supports the philosophy of only using javascript to progressively enhance the page. Although Remix is a SAP, it is possible to code-split stylesheets so they are only downloaded for a page that the user is visiting and therefore feel like modular CSS to the developers. I also tried Tailwind, which was a breeze to configure and made the little styling I added very convenient.
  • Data loaders: Since data fetching should be done on the server, it is necessary to implement so-called data loaders which later make data available to the client with the help of a useLoaderData hook. I implemented a loader for a REST API and one for GraphQL and they were incredibly easy and straightforward to write.
const RICK_AND_MORTY_REST = "https://rickandmortyapi.com/api/character";

export const loader = async ({ request }) => {
  const data = await fetch("https://rickandmortyapi.com/api/character");
  const response = await data.json();
  return response;
};
Enter fullscreen mode Exit fullscreen mode
const GetCharacters = gql`
  {
    characters(page: 2) {
      results {
        name
        id
        image
        status
        location {
          name
        }
      }
    }
  }
`;

export const loader = async () => {
  const data = await client.request(GetCharacters);
  return data;
};
Enter fullscreen mode Exit fullscreen mode
  • Form Handling: One of Remix particularities consists in how it handles forms since it is possible to execute form handlers on the server side. By doing that, we avoid useState hooks for our input components, client-side javascript for event handlers, and the famous event.preventDefault(). As a consequence, the input is responsive right from the start.
export async function action({ request }) {
  const body = await request.formData();

  const task = body.get("task");
  const assignee = body.get("assignee");

  const fieldErrors = {
    task: validateTaskLength(task),
    assignee: validateNameLength(assignee),
  };

  if (Object.values(fieldErrors).some(Boolean)) {
    return badRequest({ fieldErrors, task, assignee });
  }

  await fetch("http://localhost:8000/todos", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      title: body.get("task"),
      assignee: body.get("assignee"),
      id: uuidv4(),
    }),
  });
  return null;
}
Enter fullscreen mode Exit fullscreen mode
  • Error boundaries. While it is possible to anticipate failures and handle them, it is not easy to manage unexpected failures. Normally, when an unforeseen failure occurs, the screen just goes blank without any indication of error for the end user. To avoid that kind of behavior, React 16 implemented Error Boundaries, which allow capturing unexpected failures and showing them only inside the predefined Error Boundary while maintaining the rest of the application usable.
export function ErrorBoundary() {
  const { invoiceId } = useParams();
  return (
    <div className='text-red-500'>
      We are sorry, we couldn't display the invoice with id: {invoiceId}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Illustration of Error Boundary

Phase 3: Teach it!

With all that freshly acquired knowledge, I was starting to get excited to share it with others. I made the repository public and put together a set of slides to give a talk at my company. On the day of the talk, everybody attending was able to clone the repository and get their hands dirty with the code while I was explaining. I chose this approach to provide a more practical and hands-on introduction to the subject, but it was also important for me that everybody attending would have a working example of all the concepts to continue experimenting. We made a live transmission on Youtube, and if you understand Spanish, you can watch the recording here: https://www.youtube.com/watch?v=8Ag-lLXvR0Q

Conclusion

It’s a little unfair to compare Next.js and Gatsby with Remix since Remix had the chance to learn from the mistakes and inefficiencies of its predecessors. With that said, after my research, I can say that the creators of Remix did an excellent job in creating a framework that provides great performance combined with awesome developer experience. In my case, I will have Remix in mind when defining a stack for a new project, so I can offer the optimal solution for the particular use case in question.

Top comments (0)