Dev

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 went 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 from Next in several ways that we will explore in this article.

Not just React

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

Remix appears to be the first JavaScript framework that attempts 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 mainstream in the future. (JHipster is similar in that it abstracts the JavaScript front end while using a Java back end.)

Co-located server and client code

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

Building on web standards

If you look at Remix’s philosophy, one of its main tenets is to build upon existing web standards like HTTP and HTML and augment them with a layer of JavaScript that doesn’t obscure the underlying technologies. Evolving in close cooperation with web standards is a good idea because they do a remarkably good job of staying current with the changing development landscape. Modern web standards are also impressively robust and capable. 

A good example of Remix relying on standards while also expanding on them is its 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 communication in the browser (it absorbed most of the good ideas from those libraries). Remix uses the Fetch API in the browser. It then goes a step further by implementing an HTTP client that uses the Fetch API on the server. The net result is developers can use the same familiar API across the stack. 

Another interesting way Remix leverages standards is to use <link rel="prefetch">. Using the <link rel="prefetch"> components lets Remix preload pages when the link to them is visible, even if the page itself is dynamic. Next’s <link> component can only prefetch statically generated pages.

Progressive enhancement and forms

Progressive enhancement is 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 function. One way that Remix embraces that idea is by accepting and incorporating forms and form data into the full-stack arrangement, while implementing a write API on the back end. If JavaScript is not available, the forms will still work in the old-school way: just submit and reload the page.

Limiting network payloads

Cutting down on the amount of data—JavaScript, JSON, you name it—going over the wire is a big area of research and development in the JavaScript community. Remix has some ideas here, especially with respect to limiting the data and JavaScript that is sent.

For example, Remix can do things like load endpoints and filter their data sets on the server and bundle the consumable JSON along with the UI, instead of downloading and processing the whole set on the client. This is done by defining a loader() function alongside the React component that needs the data. The loader function runs on the back end, which reduces both the data and the UI scaffolding that must be sent and rendered or re-rendered.

Other frameworks like Next and SvelteKit have analogous features. Remix’s use of the loader function brings it in line with current ideas about how to manage data loading across 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 consumption code with any compatible back end, not just the JavaScript one that comes with Remix. Remix’s embracing of the BFF pattern acknowledges that architectures are complex. Using a back end server to orchestrate the various services that go into making an application work is often the best way to go. 

Hands-on with Remix

I’ve described some of Remix’s most compelling features, including areas where it is a model for new ideas and possibilities for JavaScript development. Now, let’s create a simple application and get a sense for how Remix does its thing.

In a command prompt, use npx to run the Remix application creator shown in Listing 1. You can fill your application using the same input seen here.

Listing 1. Create a new Remix App


$ 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: 'https://remix.run/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’s worth mentioning the concept of stacks, which you can see in my selection of the “Indie” stack in Listing 1. Stacks are preconfigured technology stacks with different deployment profiles. The Indie stack we’re using is a familiar Node plus SQLite (with Prisma) back end. Also, stacks are not hard-and-fast choices; they are just a pre-configuration of changeable options—you could migrate the Indie stack to use an edge deployment like Vercel, for example. See the Remix documentation for more about stacks.

Now we can run the application by entering: npm run dev. Once the dev server spins up, 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 application landing page.

Remix has created a simple sign-up/log-in 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 disk, you’ll see an /app directory; this is where the bulk of the code lives. Alongside /app are a few other directories: /prisma holds the mappings for the ORM framework; /cypress contains the testing config; /public has the public assets like favicon; /build contains the production build output.

Looking inside the /app directory, you’ll 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 is where you will find the app styles (by default, Remix uses Tailwind).

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

For example, the main page we see in Figure 1 is rendered by the /app/routes/index.jsx file. If you look at the contents, it is standard React with only a touch of Remix-specific code in the form of the <Link> component used for routing between the Remix pages.

If you look in /notes you’ll see a file like /notes/'$noteId.jsx'. That is the Remix convention for handling URL parameters, the $nodeId token will be replaced with what appears in the URL the user is accessing and made available to the page in a special params object as params.nodeId (params is available to the server-side loader() function)).

If you look at the /app/routes/notes/index.jsx file, you can get a sense 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/${note.id}`);
}

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

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

  return (
    <Form
      method="post"
      style={{
        display: "flex", flexDirection: "column", gap: 8, width: "100%" }}>
      <div>
        <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 } />
        </label>
        {actionData?.errors?.title && (
          <div className="pt-1 text-red-700" id="title-error">
            {actionData.errors.title}
          </div>
        )}
      </div>

      <div>
        <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 />
        </label>
        {actionData?.errors?.body && (
          <div className="pt-1 text-red-700" id="body-error">
            {actionData.errors.body}
          </div>
        )}
      </div>

      <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>
      </div>
    </Form>
  );
}

Listing 2 is a typical functional React component named NewNotePage. It defines some JavaScript functions and returns the JSX markup for the template. (Note that I have removed extraneous code like validation to simplify things.)

Notice that the template uses the Remix-specific <Form> component, which works closely with the special Remix action() function. This function is actually a server-side function. It takes a request object, which models the request info sent by the <Form> component. Notice that Remix uses a Request object that mimics the browser API even in the server functions.

The action() method uses the info 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 the shared server code from the server functions defined in the routes. Notice that the useEffect hook is harnessed to set the body and title of the document based on the Remix useActionData() function.

Conclusion

Although you’ve had just a quick tour of Remix’s capabilities, I hope the example gives you a sense of the flavor Remix brings to reactive full-stack development. Remix can do a lot more than we’ve covered here. It is definitely a framework to watch for future developments in JavaScript.

See the Remix documentation to learn more about Remix’s philosophy. Also see A Look At Remix And The Differences With Next.js for more about Remix in comparison to Next.js.

Copyright © 2023 IDG Communications, Inc.



READ SOURCE

This website uses cookies. By continuing to use this site, you accept our use of cookies.