What is Remix?
Remix is a full stack web framework based on web fundamentals and modern UX. It was created by the Remix.run team, founded by Ryan Florence and Michael Jackson. They are the creators of React Router.
Remix is a seamless server and browser runtime. It has no static site support, and always relies on a server. Remix aims to provide fast page load times and instant UI transitions.
Remix is built on the Web Fetch API, which allows it to run anywhere. It can be deployed in a serverless environment or in a Node.js server environment.
Remix features
Fast data fetching
Fetching data is so fast with Remix that there is no need for transitional spinners.
Rather than fetching data via a series of requests from components, Remix will load data in parallel on the server. It will then send the browser an HTML document that contains the data, ready to be displayed.
Making use of its own cache, Remix makes page reloads really fast. Remix will reload unchanged data from the cache, and only fetch new data.
Forms
Remix has a Form
component, which is an enhanced HTML form component. Remix's Form
component requires no onChange
, onClick
, or onSubmit
events on the form or its fields. Contrary to traditional React forms, there is also no need for useState
fields per form input.
Remix's Form
component will automatically do a POST
request to the current page route with all the form's data submitted. It can be configured to do PUT
and DELETE
requests as well. To handle requests from a form, a simple action method is needed.
Forms have traditionally been a point of frustration with React apps. Remix's approach to forms allows developers to create forms without having to write lines and lines of boilerplate code.
Routing
Similar to Next.js, Remix uses the file system to define page routes.
Remix is built on top of React Router v6. This means that all the React Router APIs can be used in a Remix application. Anything that works with React Router will work within Remix.
When navigating using a Link
tag, the Outlet
tag from React Router will automatically render the link's content on the page. This makes it easy to build a hierarchy of nested routes.
Nested routes
Nested routes allow Remix to make apps really fast. Remix will only load the nested routes that change. Remix will also only update the single nested component that was updated by some user interaction.
Nested Routes also provide nested CSS styling. This allows CSS to be loaded on a per page basis. When a user navigates away from a certain page, that page's stylesheet is removed.
Error handling
When a route in a Remix app throws an error in its action method, loader, or component, it will be caught automatically. Remix won't try to render the component. It will render the route's ErrorBoundary
instead. An ErrorBoundary
is a special component that handles runtime errors. It's almost like a configurable try/catch block.
If a given route has no ErrorBoundary
, then the error that occured will bubble up to the routes above until it reaches the ErrorBoundary
of the App
component in the root.tsx
file (assuming TypeScript is used).
Error handling is built into Remix to make it easier to do. If an error is thrown on the client side or on the server side, the error boundary will be displayed instead of the default component. This graceful degradation improves the user experience.
Project setup
Creating a new Remix project is as easy as using the following command in a terminal window.
npx create-remix@latest
You'll be asked to pick a folder name for your app. You'll also be asked where you want to deploy. Select Remix App server.
Remix can be deployed in several environments. The "Remix App Server" is a Node.js server based on Express. It's the simplest option to get up and running with Remix.
You'll then be asked to choose between TypeScript or JavaScript. The last step will ask you if you want to run npm install
. Say yes.
Once installed, Remix can be run locally by using the following terminal commands.
cd [PROJECT_FOLDER_NAME]
npm run dev
Once Remix is running, you can go to localhost:3000
in your browser to see if the default Remix installation worked. You should see the following.
Project structure
Let's explore the project structure that Remix created for us.
- The
public/
folder is for static assets such as images and fonts. - The
app/
folder contains the Remix app's code. - The
app/root.tsx
file contains the root component for the app. - The
app/entry.client.tsx
file runs when the app loads in the browser. It hydrates React components. - The
app/entry.server.tsx
generates a HTTP response when rendering on the server. It will run when a request hits the server. Remix will handle loading the necessary data and we must handle the response. By default, this file is used to render the React app to a string/stream that is sent as a response to the client. - The
app/routes/
folder is where route modules go. Remix uses the files in this folder to create the URL routes for the app based on the naming of these files. - The
app/styles/
folder is where CSS goes. Remix supports route-based stylesheets. Stylesheets that are named after routes will be used for those routes. Nested routes can add their own stylesheets to the page. Remix automatically prefetches, loads, and unloads stylesheets based on the current route. - The
remix.config.js
file is used to set various configuration options for Remix.
Any file ending with
.client.*
or.server.*
is only available on the client or the server.
Demos
The default installation of Remix comes with demos that allow us to see some of Remix's defining characteristics in action.
Forms and actions
Head over to http://localhost:3000
in your browser. Click on the Actions
link under the Demos In This App heading. This will give you a chance to try out Remix forms and their corresponding action methods. To view the code for this demo, take a look at the app/routes/demos/actions.tsx
file.
export default function ActionsDemo() {
// route component
}
// action is called on the server for POST, PUT, PATCH, DELETE
export let action: ActionFunction = async ({ request }) => {
let formData = await request.formData();
let answer = formData.get("answer");
if (typeof answer !== "string") {
return json("Come on, at least try!", { status: 400 });
}
if (answer !== "egg") {
return json(`Sorry, ${answer} is not right.`, { status: 400 });
}
// Redirect after a successful action
// prevents the reposting of data if the user clicks back
return redirect("/demos/correct");
};
The action
function above is a server-only function to handle data mutations. If a non-GET request is made to the page's route (POST, PUT, PATCH, DELETE), then the action
function is called before the loader
function. Actions are very similar to loaders. The only difference is when they are called.
CSS and Nested routes
Click on the Remix logo to go back to the welcome page. Now, click on Nested Routes, CSS loading/unloading to see how Remix allows certain CSS rules to only be included on specific routes and their children. To view the code for this demo, take a look at the files in theapp/routes/demos/about/
folder, as well as the app/styles/demos/about.css
file for the route-based CSS.
Linking to a nested page is as simple as:
<Link to="whoa">A nested About route</Link>
Linking back to a parent route from a nested route is as simple as:
<Link to="..">Go back</Link>
Routing and Error Boundaries
Click on the Remix logo to go back to the welcome page. Now, click on URL Params and Error Boundaries to see how routing and error boundaries work in Remix.
To view the code for this demo, take a look at theapp/routes/demos/params/$id.tsx
file.
Loader
This route defines a loader function that is called on the server before providing data to the route. When a route needs to fetch data from the server, Remix uses the loader
function to handle that responsibility. The loader
function aims to simplify the task of loading data into components. Contrary to Next.js, API routes are not needed to fetch data for route components in Remix.
Here is the loader function above the ParamDemo
component.
export let loader: LoaderFunction = async ({ params }) => {
if (params.id === "this-record-does-not-exist") {
throw new Response("Not Found", { status: 404 });
}
// when the user just isn't authorized to see it.
if (params.id === "shh-its-a-secret") {
// json() is a Response helper for sending JSON responses
throw json({ webmasterEmail: "hello@remix.run" }, { status: 401 });
}
// simulate an exception since lol() does not exist
if (params.id === "kaboom") {
lol();
}
// return the data to the component
return { param: params.id };
};
export default function ParamDemo() {
let data = useLoaderData();
return (
<h1>
The param is <i style={{ color: "red" }}>{data.param}</i>
</h1>
);
}
The ParamDemo
component uses a useLoaderData
hook to get the data from the loader function. In this case, that data is the id
value of the URL parameter that is passed to the route.
Error Boundary
export function ErrorBoundary({ error }: { error: Error }) {
return (
<>
<h2>Error!</h2>
<p>{error.message}</p>
</>
);
}
An ErrorBoundary
component is rendered when an error occurs anywhere on the route. Error boundaries are helpful for uncaught exceptions that we don't expect to happen. Clicking on the This one will throw an error link will trigger the ErrorBoundary
.
Catch Boundary
export function CatchBoundary() {
let caught = useCatch();
let message: React.ReactNode;
switch (caught.status) {
case 401:
message = (
<p>
Looks like you tried to visit a page that you do not have access to.
Maybe ask the webmaster ({caught.data.webmasterEmail}) for access.
</p>
);
break;
case 404:
message = (
<p>Looks like you tried to visit a page that does not exist.</p>
);
break;
default:
message = (
<p>
There was a problem with your request!
<br />
{caught.status} {caught.statusText}
</p>
);
}
return (
<>
<h2>Oops!</h2>
<p>{message}</p>
</>
);
}
A CatchBoundary
component is rendered when the loader throws a Response. The status code of the response can be checked from within the CatchBoundary
by using the useCatch
hook. Clicking on the This will be a 404 and And this will be 401 Unauthorized links will trigger the CatchBoundary
.
Conclusion
Remix is backed by some of the most talented engineers in the React community. Version 1.0 of this full stack React-based web framework was just released on November 22, 2021. It's now released under the MIT license and is open source, making it free to use.
For more Remix tutorials, check out the following: