Press "Enter" to skip to content

Intro to Remix: A leader in full-stack evolution

While not as well known as some of the larger JavaScript frameworks, Remix has earned a reputation as one that stands out. Some of the good ideas first introduced by Remix, which became open source in 2021, have been absorbed into other frameworks as well.

At the highest level, Remix is ​​a full-stack JavaScript framework in the style of Next.js: it supports server-side rendering (SSR) of a full-stack reactive JavaScript application. Beyond that similarity, Remix takes a different approach than Next in several ways that we’ll explore in this article.

not just react

Perhaps the most significant departure from Next.js is that Remix is ​​designed to abstract away its front-end implementation. In other words, it is decoupled from React. Although Remix with React is the standard approach these days, it is possible to use different front-end frameworks like Svelte and Vue.

Remix seems to be the first JavaScript framework that tries to abstract the reactive front-end. This approach, of making the front-end framework a pluggable piece of architecture within a larger application framework, could become more common in the future. (JHipster is similar in that it abstracts away the JavaScript front-end while using a Java back-end.)

Collocated server and client code

Remix makes it easy to co-locate the code that renders the front-end along with the code that provides its data. As you will see, Remix includes a <Form> component, which is like a <form> but with superpowers to interact with server-side logic. That makes it easy to encapsulate the entire CRUD cycle of data within the same file.

Based on web standards

If you look at Remix’s philosophy, one of its main principles is to take advantage of existing web standards like HTTP and HTML and augment them with a layer of JavaScript that doesn’t hide the underlying technologies. Evolving closely with web standards is a good idea because they do a remarkably good job of keeping up with the changing development landscape. Modern web standards are also impressively robust and capable.

A good example of how Remix builds on standards while extending them is the use of the Fetch API. The Fetch API is part of modern browsers and has become the standard mechanism for issuing network requests. It has largely obviated the need to rely on third-party libraries for in-browser communication (it absorbed most of the good ideas from those libraries). Remix uses the Fetch API in the browser. It then goes one step further by implementing an HTTP client that uses the Fetch API on the server. The net result is that developers can use the same familiar API across the entire stack.

Another interesting way Remix takes advantage of standards is to use <link rel="prefetch">. Using the <link rel="prefetch"> Components allow Remix to preload pages when the link to them is visible, even if the page itself is dynamic. following <link> The component can only preload statically generated pages.

Progressive improvement and forms

progressive improvement it’s the idea that JavaScript should be a nice addition to an application, but not an essential element. If the JavaScript is not available for some reason (which is more common than many people realize), the website should still work. One way Remix embraces that idea is by accepting and incorporating forms and form data into the full-stack array, while implementing a write API on the back-end. If JavaScript is not available, the forms will continue to work the old-fashioned way: just submit and reload the page.

Limiting network payloads

Reducing the amount of data (JavaScript, JSON, whatever) going over the wire is a major area of ​​research and development in the JavaScript community. Remix has some ideas here, especially regarding limiting the data and JavaScript being sent.

For example, Remix can do things like upload endpoints and filter your data sets on the server and bundle the consumable JSON together with the UI, instead of downloading and processing the entire set on the client. This is done by defining a loader() work together with the React component that needs the data. The loader function runs on the back-end, reducing both the data and UI scaffolding that must be submitted and rendered or re-rendered.

Other frameworks like Next and SvelteKit have analogous features. Remix’s use of the loader function aligns with current thinking about how to manage loading data on the stack.

Remix and the BFF pattern

Remix supports the Backend For Your Frontend (BFF) pattern, which allows you to use the front-end and its API consuming code with any supported back-end, not just the JavaScript that comes with Remix. Remix’s adoption of the BFF pattern recognizes that architectures are complex. Using a back-end server to orchestrate the various services that go into running an application is often the best way to go.

Practice with Remix

I’ve outlined some of Remix’s most compelling features, including the areas in which it’s a model for new ideas and possibilities for JavaScript development. Now, let’s create a simple app and get an idea of ​​how Remix does its job.

At a command prompt, use npx to run the Remix app builder shown in Listing 1. You can populate your app using the same input you see here.

Listing 1. Create a new Remix application

$ npx create-remix@latest
? Where would you like to create your app? ./my-remix-app
? What type of app do you want to create? A pre-configured stack ready for production
? Which Stack do you want? (Learn more about these stacks: '') Indie
? TypeScript or JavaScript? JavaScript
? Do you want me to run `npm install`? Yes
⠙ Migrating template to JavaScript…Processing 10 files...

preconfigured stacks

It is worth mentioning the concept of batteries, which you can see in my selection of the “Indie” stack in Listing 1. The stacks are preconfigured technology stacks with different deployment profiles. The Indie stack we are using is a familiar Node plus SQLite back-end (with Prisma). Also, stacks aren’t hard and fast options; they’re just a preconfiguration of tweakable options – you could migrate the Indie stack to use an edge implementation like Vercel, for example. See the Remix documentation for more information on stacks.

Also Read:  GitHub Copilot update includes security vulnerability filtering

Now we can run the application by entering: npm run dev. Once the development server is activated, we can visit the application at localhost:3000. You should see the landing page shown in Figure 1.

A screenshot of a Remix full-stack JavaScript application. IDG

Figure 1. A Remix app landing page.

Remix has created a simple registration/login flow. Once you create a dummy user and log in, you can click the “View Notes” link to access a classic TODO app.

If you look at the directory created on the disk, you will see a /app directory; this is where most of the code lives. Next to /app are some other directories: /prisma contains the mappings for the ORM framework; /cypress contains the test configuration; /public has public goods like favicon; /build contains the production build output.

looking inside the /app directory, you will see several utility files used by the framework and a handful of directories:

  • /models contains the JavaScript data models for Prisma.
  • /routes contains the route definitions for the application.
  • /styles This is where you’ll find the application’s styles (by default, Remix uses Tailwind).

Of these, the /routes The file is the most important, as it defines both the available routes and the code that implements them. This is analogous to Next.js /app either /pages directory, where the directory structure models URL paths.

For example, the main page that we see in Figure 1 is represented by the /app/routes/index.jsx archive. If you look at the content, it’s standard React with just a hint of Remix-specific code in the form of <Link> component used for routing between Remix pages.

if you look inside /notes You will see a file like /notes/'$noteId.jsx'. That’s Remix’s convention for handling URL parameters, the $nodeId token will be replaced with what appears in the URL accessed by the user and will be available to the page in a special params object like params.nodeId (params is available for server side loader() function)).

if you look at the /app/routes/notes/index.jsx file, you can get an idea of ​​how the application handles client/server data interactions, as shown in Listing 2.

Listing 2. notes/index.jsx

import { json, redirect } from "@remix-run/node";
import { Form, useActionData } from "@remix-run/react";
import * as React from "react";

import { createNote } from "~/models/note.server";
import { requireUserId } from "~/session.server";

export async function action({ request }) {
  const userId = await requireUserId(request);

  const formData = await request.formData();
  const title = formData.get("title");
  const body = formData.get("body");

  const note = await createNote({ title, body, userId });

  return redirect(`/notes/${}`);

export default function NewNotePage() {
  const actionData = useActionData();
  const titleRef = React.useRef(null);
  const bodyRef = React.useRef(null);

  React.useEffect(() => {
    if (actionData?.errors?.title) {
    } else if (actionData?.errors?.body) {
  }, [actionData]);

  return (
        display: "flex", flexDirection: "column", gap: 8, width: "100%" }}>
        <label className="flex w-full flex-col gap-1">
          <span>Title: </span>
          <input ref={titleRef} name="title"          className="flex-1 rounded-md border-2 border-blue-500 px-3 text-lg leading-loose" aria-invalid={actionData?.errors?.title ? true : undefined} aria-errormessage={ actionData?.errors?.title ? "title-error" : undefined } />
        {actionData?.errors?.title && (
          <div className="pt-1 text-red-700" id="title-error">

        <label className="flex w-full flex-col gap-1">
          <span>Body: </span>
          <textarea ref={bodyRef} name="body" rows={8}          className="w-full flex-1 rounded-md border-2 border-blue-500 py-2 px-3 text-lg leading-6" aria-invalid={actionData?.errors?.body ? true : undefined} aria-errormessage={ actionData?.errors?.body ? "body-error" : undefined }8 />
        {actionData?.errors?.body && (
          <div className="pt-1 text-red-700" id="body-error">

      <div className="text-right">
        <button type="submit" className="rounded bg-blue-500 py-2 px-4 text-white hover:bg-blue-600 focus:bg-blue-400">Save</button>

Listing 2 is a typical functional React component called NewNotePage. Defines some JavaScript functions and returns the JSX markup for the template. (Note that I removed the extraneous code like the validation to keep things simple.)

Please note that the template uses the Remix specific feature <Form> component, which works closely with the special Remix action() function. This function is actually a server-side function. Takes a request object, which models the request information sent by the <Form> component. Note that Remix uses a request object that mimics the browser API even in server functions.

He action() The method uses the information from the Request object to make a call to the createNote() function that is imported from "~/models/note.server", where Remix has generated the shared back-end Prisma code. This is the general pattern of accessing shared server code from server roles defined in routes. Notice that the useEffect hook is leveraged to set the body and title of the document based on the Remix useActionData() function.


Although you’ve just taken a quick tour of Remix’s capabilities, I hope the example gives you an idea of ​​the flavor Remix brings to full-stack reactive development. Remix can do a lot more than what we’ve covered here. It’s definitely a framework for looking at future developments in JavaScript.

See the Remix documentation for more information on the Remix philosophy. See also A look at Remix and the differences from Next.js for more information on Remix versus Next.js.

Copyright © 2023 IDG Communications, Inc.

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *