Introduction to Remix: A Fullstack React Framework

Claire Chu
7 min readNov 29, 2023

--

What is Remix?

Remix is a React framework designed for server-side rendering (SSR). Positioned as a full-stack web framework, allowing developers to build both backend and frontend within a single app. Unlike vanilla React, where data is fetched on the frontend and then rendered on the screen, Remix fetches data on the backend and serves the HTML directly to the user. It provides fully rendered pages on initial load, offering a quick and complete user interface experience.

One of the important concepts in Remix is routing. Kent C. Dodds, the director of developer experience at Remix once described it as:

“[Remix is basically like] v7 of React Router. It’s like an upgrade of React Router that supports server rendering. It’s a compiler.”

Remix was made open-source in October 2021, and was acquired by Shopify in 2022.

Installation and Project Structure

Use this command line to get started with creating a remix app:

npx create-remix@latest name-of-your-app

The directory of the app contains the following folders and files. Among them, entry.client.jsx and entry.server.jsx are managed by Remix, and it’s better not to edit them.

  1. The app folder contains our main app logic.
  2. All the folders and files under the routes folder are exposed to the public and can be accessed with a URL.
  3. app/root.tsx is the root of our project. It describes the structure of the HTML on our index page. It also contains the general page layout.
  4. entry.client.tsx file gives us full control over the “hydrate” step (taking something static like HTML and connecting it with JavaScript to make it dynamic) after JavaScript loads into the document.
  5. entry.server.tsx file allows us to handle requests and render the entire server app. An entry server takes all the components in the application and renders them as a string, giving us full control over the way the markup is generated and sent to the client.
  6. The public folder contains public assets like static images and favicons.
  7. The build folder contains assets generated during the build process.
  8. remix.config.ts is where we configure our remix project, such as the app directory, which we can change from app to something else if we don’t like the name app, or publicPath, the port it should run on in development mode, and so on.
  9. remix.env.d.ts and tsconfig.json allow us to reference some types and contain some compiler options.

Routing and Layout

Pages inside the routes folder in Remix are nested within the route instead of being separate, allowing components to be embedded into the parent page.

Subroutes can be created directly inside the routes folder. To create a parent route, a folder with the route name should be created inside the routes folder, containing an index.tsx file.

Nested Routes

You can create nested routes by using dot delimiters. If the part of the filename before the dot matches another route filename, it automatically becomes a sub-route under the corresponding parent.

When introducing nested routes, including an index route (with a leading underscore in the file name) can ensure that content is displayed within the parent’s outlet when users directly visit the parent URL.

Nested routes (source: https://remix.run/docs/en/main/file-conventions/routes#nested-routes)

Nested Layouts

Nested layouts are used to avoid repeating the same style for multiple pages. When a file with the same name as the folder exists inside the routes directory at the same level, it becomes the layout for that folder.

To opt out of nesting for one of the child routes, you can add a trailing underscore on the parent segment.

Nested URLs without layout nesting (source: https://remix.run/docs/en/main/file-conventions/routes#nested-routes)

Nested routing involves mapping routes to segments of the URL, and creating a common UI for each route. For example, the Root component in Remix maps to the root URL, representing the common UI across all routes, while the Outlet component acts as a wrapper for nested routes, creating a space to be filled by child routes.

Remix optimizes navigation by updating only the section of the UI or the data that needs to change, rerendering persistent components across URLs and reducing the loading time. Data dependencies, actions, and styling can also be mapped to nested routes, allowing efficient fetching of data for changing routes.

Fullstack Data Flow

Bridging the Client Side and the Server Side

Managing network chasm (Source: https://www.mattstobbs.com/introduction-to-remix/)

In an application, there is typically code that runs on the client and the server. To run things in the client, data needs to be fetched and processed on the frontend. This leaves the developers to juggle asynchronous code, cache data using React states, and deal with difficult bugs and larger bundle sizes.

Remix addresses the gap between client and server code execution by allowing developers to write both frontend and backend code in the same file, simplifying the process of moving code from the client to the server and only sending what the client needs over the chasm. This approach reduces the time and cost associated with crossing this gap, minimizing loading spinners and streamlining asynchronous code management.

Data Loading

With the loader function and the useLoaderData hook, Remix decouples fetch initiation and result reading for optimization. By co-locating the loader and component, easy sharing of TypeScript types between them is also enabled.

Writing both frontend and backend code in the same file, along with shared typescript type.

Similar to Next.js’ getServerSideProps, Remix utilizes a loader function that runs on the server side when a page is requested, taking a native Request object from the Fetch API (This is part of the Remix philosophy that it emphasizes using native web technologies).

The useLoaderData hook then retrieves data returned by the loader function for rendering on the frontend, and it can be used from any component in the current route. Additionally, Remix can use this to prefetch data for routes before a user clicks a link, providing a snappy navigation experience.

State Management

Although Remix supports the integration of various React libraries, including state management tools like Redux and Recoil, its server-side rendering nature can lead to state loss during page refreshes or transitions.

Therefore, in such server-side rendered applications, state management is often sidestepped in favour of storing data, like user tokens, in cookies. This data is then sent to servers, which respond with user data through props to the page. This is because the server cannot read the browser’s local storage.

Form Handling

Instead of relying on modern techniques like onClick or onSubmit with HTTP calls, Remix provides functions such as action and loader for server-side operations, similar to the more traditional approach of using PHP for form handling.

The action function is exported by the route, and the useActionData hook is used to retrieve data from the server

The action function is exported by the route and called upon form submission, and it receives a Request object, allowing the retrieval of form data in the standard manner.

The useActionData hook is for working with data associated with the action function. These functions effortlessly access form data, eliminating the need to send JavaScript to the frontend for form submission.

Form Component

Remix introduces a Form component, an enhanced version of the standard HTML form element, designed with progressive enhancement principles. Progressive enhancement is a strategy in web design to provide a functioning app to as many users as possible, while also providing each user with the best possible experience based on their browsers’ features.

This approach ensures that the form remains functional even for users without JavaScript or those with slow internet connections, as it still sends requests to the server. For users with JavaScript, the form is further enhanced using fetch, providing a smoother experience without page refreshes on form submission.

Here is an example of a form using the POST method. Upon submission, it sends a POST request to the route matching its action attribute:

A Form component sending a POST request to the contact-us route

The information from the form fields can be accessed using the standard request.formData() API, and the name attribute on the inputs corresponds to the formData.get(fieldName) getter.

Accessing form data by getting values from the corresponding name attributes.

This co-location of components, data reads, and data writes simplifies error handling in the action — any encountered unhappy paths involve returning or throwing a response, while Remix manages the rest.

References

--

--

Claire Chu

Currently a front-end developer in the VR industry. Formerly a museum worker with an MA degree and working experience in the UK.