Learning Remix: Full-Stack Nextjs Alternative [2025 Tutorial]

Profile Picture of Rafael Goulart
Rafael Goulart
Senior Full-stack Developer
A developer learning the Remix framework from a hammock

Many programmers, including myself, have hobbies or side projects that are tech related. Which at least somewhat explains why I decided to spend my recent vacation learning some new tech (but don’t worry, I also spent time doing nothing). After digging into a number of trends, I stopped on a web framework that piqued my interest: Remix. It’s a Next.js alternative that’s curious because it sits between basic and modern, simple and powerful, and together this makes a refreshing development experience. 

In order to learn more about it, I decided to approach it  with the mantra of old forums: RTFM (or, said more politely, I read the whole manual). After reviewing some video tutorials and articles, I got my hands dirty. In this article, I’m going to share my findings with you: the key features of Remix, new concepts it introduces, and of course, the pros and cons.

First things first, let’s talk about what Remix is (and what is not).

Table Of Contents

What is Remix? The Full-stack Web Framework Taking on Next.js

Remix development started in early 2020, and the first open-source version (v1.0) was released in November 2021.

Before bringing that to the table, however, it’s important to understand what Remix is and what it is not, so we can compare to the more well-known alternatives cited above.

Need to hire full-stack developers?
We have thousands of qualified remote developers with React experience
Hire Developers

Remix vs Next.js vs Gatsby

The team behind Remix says it is a full-stack web framework. Similarly, Next.js is a React framework for building full-stack web applications, while Gatsby is a static site generator. Regardless of whether they’re similar or different in their definition, they are certainly different approaches to solving web development problems.

Remix, Next.js and Gatsby logos

All of them try to solve one specific problem: avoid bloated SPA (Single Page Application) web applications and all their caveats. Even React is deprecating its Create React App in favor of a framework approach. These frameworks try to be faster by delivering only the code that is essential to the browser: only what is needed for a page (HTML + JS + CSS + assets), only data needed for the context, and a better experience for SEO (search engine optimization). On top of that, they allow aggressive caching and CDN support.

Comparison table of Remix, Nextjs, and Gatsby, full-stack web frameworks

For the record: SPAs are not bad per se, but even small applications can suffer performance issues using this approach. It has great use cases, though, and can be a fit for many projects. At the time of this writing, Remix is actually adding unstable support for SPAs.

Gatsby, as the definition makes clear, generates static pages on top of React and GraphQL. In contrast, NextJS allows not only SSG (static site generation, like Gatsby) but also SSR (server-side rendering).

SSG (Static Site Generation) vs SSR (Server-Side Rendering)

SSG works in a similar way to Gatsby, as the page is pre-rendered at compile time. This process happens fast: the browser gets the final version of the page, which can be cached and also used behind a CDN (Content Delivery Network) to load a page from a server near to the end user. This works well when data is static or changes infrequently, like static web pages (e.g. home, features, etc), or blog articles. Using SSG, you can continue to load additional data and front-end iterations after the page is delivered, such as comments for a blog article.

SSR, on the other hand, is a fit when you have dynamic content. In this case, the page is also pre-rendered and makes use of caching, but the data is loaded according to the params passed. As well, unlike SSG, pre-rendering in SSR does not happen on build time. A good example of this would be an e-commerce website with millions of products; you cannot pre-render all the pages, as the content would become outdated immediately – not to mention it would take a significant time to load. Customer searches and product listings, for example, are cases covered by server-side rendering, while a news site could be covered by either SSG and SSR, or a mix of them, depending on the strategy used.

One may immediately conclude that Gatsby has narrower use cases than NextJS, though it’s quite competent on what it covers. Conversely, NextJS embraces wider use cases, but comes with more complexity and some deploy caveats. 

How can we fit Remix between the above frameworks? Well, we could say that Remix covers most of the same use cases, but it seems easier to compare Remix to NextJS than to Gatsby. Both Remix and NextJS aim to be in the space of “full stack” and “web framework”. Remix implements SSR but doesn’t support SSG; instead it uses stale-while-revalidate caching directive, claiming to achieve the same results. It’s worth noting that this is native to the HTML protocol and not unique to Remix.

Remix: A Simpler JavaScript Framework

At first, Remix looks like you’re stepping back to the roots of web development. Some people accuse it of being like a refactored, old-stylished PHP. But, as PHP still pays my bills and remains a rock-solid foundation for a large part of the world wide web, this can be even considered a compliment (sorry, haters).

Instead of a step back, it’s more like a simplification. Remix uses:

  • standard web protocols;
  • a (configurable) out-of-the box NodeJS server;
  • a browser build (front-end); 
  • an asset manifest; and
  • an HTTP Handler and adapters built on the Web Fetch API aside.

This is so “basic” that it can be deployed virtually anywhere (more on that later).

Schematic of how Remix allows you to write front-end and back-end code in the same file, making it easier to cross the "network chasm"

An “Agnostic” Framework

Remix is agnostic in the sense that it doesn’t care from where or how you load your data, nor does it care about your models. You can directly query your database using a NodeJS database client, an ORM (Object–relational mapping) like Prisma or Sequelize, a REST API, a GraphQL query, or a JSON data file. Your choice. 

Remix, built on top of React Router, supports client-side routing along with its comprehensive server-side rendering features. So routes can change only a section of a page without reloading the whole page. Assets don’t need to be reloaded. Data fetched is much smaller.

The Important Difference Between Remix and “Old-Fashioned” Web Programming

Again, the comparison to old-fashioned PHP (or even Rails) plays an important role here. Both have to process on the back-end and deliver the front-end code to the browser, while making the iterations continue using a different language: JavaScript. If you’re not old enough to remember macaroni code mixing HTML, CSS, <?php echo $myVariable ?> tags and Javascript, you have no idea how wild the standard programming experience was. Remix seems to bring this mess back, but with an important difference: it’s all JavaScript, and on top of web standards. All this with all modern NodeJS and React ecosystems at your disposal. 

That’s why Remix defines itself as a web framework. It’s full-stack as you’ll control both back-end and front-end on the same code. Remix embraces the web platform – its conventions, APIs and standards – while still embracing cutting edge technologies. 

We wrote a lot of “web” here. Save the word web – we’ll be back on that later.

 A quote from Linus Torvalds, creator of Linux kernel

Remix Highlights and Key Concepts

Remix is made up of four key components:

  1. A compiler 
  2. A server-side HTTP handler 
  3. A server framework 
  4. A browser framework
List of Remix key components and main features

Starting a New Remix Project

A Remix project can be started using either JavaScript or TypeScript. You can do a quick start, but the documentation suggests it’s more productive to start with a Remix template or stack. Templates allow you to bootstrap a minimum structure for your project. A more detailed / opinionated template is called stack. There are some official templates/stacks, as well as ones maintained by the community.

Choosing the right template/stack depends on some important considerations: 

  • How and where are you going to deploy? NodeJS/Express based server, Deno, Cloudflare, Arc, and Fly have official templates, while Netlify and Vercel maintain their own templates.
  • Which development stack will you use? Database, ORM, UI framework, etc.
  • How big do you want to go? A large project or complex project can requires either a more flexible or opinionated starting point.

If you’re unsure, you can browse the list of community stacks on GitHub or the Remix Guide Templates list. But if you feel a bit lost, the Remix Official Stacks are a good starting point.

Remix Example Project: The Indie Stack [Tutorial]

In my study I chose The Indie Stack, which delivers a small notes system app with authentication and registering using Typescript, SQlite Database, Prisma ORM, Tailwind for styling, a Docker setup, unit / end-to-end test setup and more. 

To install it, type the following on your console and follow the instructions:

Then to start a development server:

Ready to browse on http://localhost:3000

Screenshot of Indie Stack, an example Remix project

For the record, if you are a Yarn, PNPM or even a Bun person, you can alternatively run:

Folder Structure

What we see at first isn’t different from many similar tools we know, plus the specifics for technologies added by the stack:

Remix-specific folders and files are the following:

The public folder is pretty straightforward: the place for any asset requested by the browser (fonts, images, final CSS and JS). The /app/ is where our code lives:

A vigilant eye might notice a pattern here: files ending with server.ts and client.ts. As the words implies, it restricts the execution of the code to either server or client side. Mixing them on the same folder looks weird at first, but we quickly discover that mixing server and client code on the same file is desired. NextJS does this, too, but not as deep. More on that right away, because we’re entering in the heart of the Remix’s most important pattern in action: the /app/route folder, or the routing system.

Root File and Routing

Unlike other routing systems where URLs like /sales/invoices/102000 are related to a page, “each segment – sales, invoices, and 102000 – can be associated with specific data points and UI sections.” (Remix – Route Configuration – Modular Design) If a segment shares the same “root” (base segment, in this case, sales) it also shares the same layout: it only needs to load its own content. 

Let’s check our /app/route folder:

On this listing, there’s an extra file outside /app/route folder: /app/root.tsx. This is the initial entry point of our application (for the sake of brevity, we’re ignoring Typescript):

Lots of things are happening here. Checking the imports, the first important thing to notice is @remix-run/node. On the previous topic, while discussing which template/stack to choose, one of the questions we asked was, “how and where are you going to deploy?” If you choose Deno or Cloudflare, it’s a different import here and in some other server files. 

Another important import is @remix-run/react. There are many special React components provided by Remix. Three exports: links, loader, and default. The first two are server side, while the last is client side, or the React component to be rendered. Remember, this is the base root, so everything here applies to all pages. But pages can also have the same functions, and also add their own meta, links, etc. <Meta /> and <Link /> handles that. On the body, <Outlet /> component marks the entry point for nested content. <LiveReload /> is used in development mode for hot reloading, and ignored in production.

The next file to be called after that is the site’s root: /app/routes/_index.tsx. This is a cropped version of it (Tailwind doing its magic):

Two exports: the new meta and a default. The first defines anything this page wants to appear on meta tags (being injected by <Meta /> component on the root file) and default defines the content to be injected into <Outlet />. The lasting news is <Link /> – a special component to control front-end navigation.

Sample CRUD Analysis

Speeding up a little, there are links to Sign up and Login. Once an account is created, /notes can be browsed:

Screenshot of a section of the remix example app

The  /app/route/notes.tsx file is called (cropped version):

Again, two functions, but now loader and default. The second does the same as before, defines the  content to be injected into <Outlet />. Now loader, as the name suggests, loads data to be used on the React code. Some Prisma code to get data (using models, and this is server side code, so .server.ts), the user is retrieved from the session (again, using a server side code), and everything is returned as JSON. On the client side there’s a hook to get this data: useLoaderData(). Simple enough.

A special component <Form /> is used instead of a plain HTML tag. The syntax is pretty much the same but what happens on /logout, or on the file logout.tsx is different:

Until now, we’ve only talked about HTTP GET. Now, there’s a POST. This method is handled by the exported function action. So loader is called on GETs and action is called on POSTs? Yes, but it doesn’t stop there: loader is called again, and if it’s the case, re-fetches data and updates client side. In this case, it redirects to home. Let’s create a note:

Screenshot of a note in an example Remix app with a form component

The “empty” note route /notes/new calls the the file /app/route/notes.new.tsx:

There’s no loader as no initial data is needed for this segment. Instead of useLoaderData() there’s a useActionData() – this is a GET, not a POST, so this is null. On useEffect() and in the form the variable actionData?.errors? is referred. But wait – does that mean the form validation is being done on the back-end? Of course, back-end validation is a must for security reasons, but what about fron-tend usability? Shouldn’t we validate on both back-end and front-end?

The answer to this starts with another question: what if we could do both in one place? Let’s save without filling the form:

Screenshot of Remix example app, showing a required field message in a form

A closer look on the network tab helps us to understand what happened:

Remix app view from the network tab showing what happens with a required field in a form

The POST failed with 400 Bad Request (plain web standard for a form), the response has the errors to be displayed, and loaders from the other segments are refreshed. No HTML or Javascript are reloaded, only data, and only needed data. The action method is responsible for the logic:

Data is retrieved from the request and validated; if there’s any errors, a quick response is returned, with 400 HTTP status code. The validation is of course pretty simple, but it can be done using any validation library or method the programmer defines.

And only once for both back-end and front-end. No need for a different logic. If your ORM triggers validation errors (like Mongoose validation) you can use that to directly display on the front-end without any extra effort. One single point of failure. That’s a huge flow simplification.

Let’s save a valid record:

Remix sample app showing a valid record of a form component

Let’s check the network tab for server requests:

Remix app view from the network tab showing server requests

/notes/new is a POST, followed by 3 data requests – each loader on the page, including the one to refresh note’s list on the sidebar. The last request is the UI slice from the route /notes.$noteId.tsx – only new fresh UI content needed as the sidebar and header are already in place.

Remix vs React: Why it Seems Awkward at First

If you’re like me and have been a React user for years, you notice an important item hasn’t come up. Something you use on a daily basis and struggle to keep consistent in large codebases to avoid prop drilling when triggering changes in different places on the screen. Even using external libraries to optimize multiple reactions?

Where is useState, useContext? Or Redux? Or Zustand, or Jotai, or name-your-state-library–here?

In short: where are the states?

In simple cases like in the sample code we went by, you don’t need a state manager, you just keep in sync with the server. You’ll use states in components – and it’s up to you to use whatever fits your needs – but as on each interaction with the server you refresh the loaders, it’s simpler, safer, faster, just to rely on the server data.

Let’s consider that the same application is using GraphQL on the front-end to create the note, and display a new note page with the updated sidebar. There is more than one way to do this of course, but one usual way could be:

  • Request a POST with the GraphQL mutation
  • Validate the input, save on the database and redirect to /notes/{id}
  • Load the new page (the whole page)
  • Get data for the sidebar and the note to be displayed (this can be sent together with the page or requested after load)

On Remix, the method would be: 

  • POST form data
  • Validate the input, save on the database and “redirect” to /notes/{id}
  • As the page has the same layout, only the specific UI content for displaying the note is loaded, not the whole page (“redirect” here does not need a full page refresh)
  • Get data for the sidebar and the note to be displayed

Another important difference is that on Remix you don’t need to explicitly load the sidebar data: it’s part of the layout, so each loader in the page context is called in cascade.

You can get a similar behavior with NextJS – changing the route without refreshing the whole page – but with extra complexity, including loading the “display note UI” upfront, even if a note is not created. Remix provides a simpler incremental approach.

Recapping my Findings: Remix as a Full-Stack Web Framework

To bring it full circle, I said earlier that Remix emphasizes being a web framework. This is by design: built on top of web standards is the choice for a simpler approach to solve web application problems. As a consequence, we have strengths and limitations, depending on your needs and expectations. Below is a summary of my findings.

It has a Low Learning Curve

You know how the web works, you know how to GET and POST, you know React: you’re ready for Remix. However, the routing system can be awkward at first – though it can be easily mastered. 

You Can Deploy Virtually Anywhere 

You choose your desired Javascript runtime and deploy wherever they are available. Remix itself is not a server, but runs on top of a server, so you just need to select the appropriate adapter. As we see while reviewing the routing system, switching from NodeJS to Deno, for instance, is just a change on imports, and the rest of the code it’s exchangeable between runtimes. Supporting multiple runtimes can be achieved with minor effort of configuration.

It’s Full-Stack

Depending on your skills (or your team’s organization) this can be considered either a pro or a con. Remix organizes the back-end and front-end code together, so if you’re focused on only one side of the coin – either back or front-end – it can be confusing. Same applies if your team has a strong separation on backend and frontend tasks.

If you consume an already established API (or GraphQL endpoint, etc) maybe this is not a big deal. If your front-end team is used to making API requests, moving that to loader / action can simplify their work.

There’s no Convention for API Endpoints

Or GraphQL, or any backend data endpoint. 

Remember Remix is not a server, so unlike NextJS (that has conventions to API endpoints) you can do whatever fits you. The Indie Template runs on top of NodeJS and Express, so you can build a REST API on top of that, or a GraphQL server using ApolloGraphql  (or any other GraphQL server library). The same freedom to choose how to load data applies to how you organize your data – even in a separated project.

No Support for Desktop Application

A common trend is to build your web application using the same codebase you build your desktop application. With the help of Electron, Tauri or NeutralinoJS a project can run on multiple platforms and operational systems.

This is a limitation by design. Remix does not and probably never will support this feature. The reason is simple: it’s based on web standards, so a desktop application should be a web server. Electron applications are the front-end consuming a remote API.

Although it’s listed here as a con, please consider this is by design: Remix has a target environment and this allows simplicity. Not trying to solve all the development problems but instead being competent on a specific area can also be considered a feature. 

Conclusion

In a world where complexity seems to take over all aspects of programming, it’s refreshing to have a tool that goes in the opposite way. Remix doesn’t try to embrace all use cases or to be a general solution. Instead, it aims to simplify the path for the well-defined problem of the web application.

Remix is still a young alternative. But it learns from the caveats of its predecessors and proposes smart solutions. My conclusion? Remix worth it to give it a try and keep it in mind for future projects.

Originally published on Feb 19, 2024Last updated on Jan 10, 2025

Key Takeaways

What is Remix JS used for?

Remix.js is a full-stack web framework that facilitates the development of server-rendered React applications. It emphasizes server-side rendering (SSR) and provides a unified routing system that works both on the server and the client. Remix is an alternative to Next.js and Gatsby, and was developed with the goal of delivering better user experiences.

Is Remix better than Nextjs?

When deciding between Remix and Next.js, so the one you choose will depend on your project’s size and complexity.
Both Remix and Next are intended for web applications with static and dynamic content, but offer different approaches to solving development challenges. The one you use will depend on the size and complexity of your project. Remix is great for applications that are relatively small and straightforward. On the other hand, Next is a suitable choice for larger, more advanced projects that require long-term stability and strong performance. Because of it’s flexibility, scalability, and support for reusable REST APIs, Next is a solid contender for projects that involve a lot of data interactions.

Why Remix is better than React?

Remix is built on top of React, so all of React’s strengths - including it’s mature community and rich ecosystem - are available in Remix. In addition, Remix offers a quick project bootstrap, server side interaction based on web standards, a faster initial load time, and better SEO (search engine optimization), as the server does most of the heavy lifting. Lastly, Remix has a low learning curve for those already familiar with React.

What are the drawbacks of Remix?

While Remix offers a faster initial load time and better SEO for web applications, it has some limitations to be aware of. First, the shift away from client-side state may not be suitable for some projects, particularly those requiring chat-based functionality. Additionally, the need to fetch data for most visible routes can increase the number of server requests. This can translate to increased costs, and Remix therefore requires a nuanced testing approach reliant on end-to-end testing for comprehensive coverage.

Looking to hire?

The Scalable Path Newsletter

Join thousands of subscribers and receive original articles about building awesome digital products