Today we explore remix (@remix_run), a new react framework which strives to use platform APIs as much as possible, and helps us to build resilient, performant and accessible full stack web applications.
In this blog, I will try to explain some of the features of the remix and will implement them in a to-do app.
Do not hesitate to reach out through our social media or via email to ask any questions!
Prerequisite
- Knowlege of JavaScript and react
- Knowledge of HTML/CSS
Development Setup
Make sure your development environment has following dependencies installed.
- Node version greater than 14
- npm version greater than 7
Project Setup
Create a new remix app
npx create-remix@latest
This script will ask you couple of questions, like where to create the app.

Let's say explore-remix.
The next question will be

Let's select Remix App Server for now.
Let's select TypeScript.

and press Y for installing dependencies. It will install all required dependencies, after that run the following command to change directory.
cd explore-remix
Let's run the following command to start the development server.
remix run dev
Start Building
Open app/root.tsx
file, there is a lot going on in this file. But don't worry we will explore each bit. Replace the content of the file with following:
// app/root.jsx
import {
Links,
LiveReload,
Outlet,
ScrollRestoration,
} from "remix";
import type { LinksFunction } from "remix";
import globalStylesUrl from "~/styles/global.css";
export let links: LinksFunction = () => {
return [
{ rel: "stylesheet", href: globalStylesUrl }
];
};
export default function App() {
return (
<Document>
<Outlet />
</Document>
);
}
function Document({
children,
title
}: {
children: React.ReactNode;
title?: string;
}) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
{title ? <title>{title}</title> : null}
<Links />
</head>
<body>
{children}
<ScrollRestoration />
{process.env.NODE_ENV === "development" && <LiveReload />}
</body>
</html>
);
}
Here I would like to explain <Links/>
, <Meta/>
and <Outlet/>
components.
Links Component
It is a remix component, which allows us to add all sorts of html
link
tags like stylesheet links and this is the primary way of adding styles to pages in remix. To add link
tags we export a links
function form our route file listing all the required links
as shown above. The cool things about this approach is remix will only add these links
when the route is active, when the route is not active these will be removed, that way we won't encounter CSS
naming conflicts. Read more here.
Outlet Component
This is primarily react-router
component, which is used in parent component to render child layouts. And ofcourse remix uses react-router as its routing dependency.
The <LiveReload />
component is useful during development to auto-refresh our browser whenever we make a change. Because our build server is so fast, the reload will often happen before you even notice.
First delete the app/routes/demos
folder then open app/routes/index.tsx
file, this route will be the root route of your application. Replace the content of this file with the following:
// app/routes/index.tsx
export default function Index() {
return (
<div>
Hello world from remix
</div>
);
}
Now when you visit the root route that is http://localhost:3000
you will see the following output:

Routing
Let's talk about routing in remix. This is the most important concept in remix and routes in remix corresponds to directory structure. We will be creating bunch of routes in our todo app to list all todos, to find todo by id and to create a new todo.
We will be adding following routes to our app
/todos
/todos/${todoId}
/todos/new
Let's do that! Create app/routes/todos
directory and create an index.tsx
file like app/routes/todos/index.tsx
. Index route will be executed when the url matches /todos
and add the following jsx in the index file
// app/routes/todos/index.tsx
export default function TodoIndex() {
return (
<div>
<h2>My Todo's</h2>
<ul>
<li>Prepare breakfast</li>
<li>Often Prayer</li>
</ul>
</div>
)
}
Now if you go to http://localhost:3000/todos
you will see the todos rendered like

As we don't have real data coming from database, let's do that real quick. For that purpose we will be using loader
function from remix. Let's see it in action
// app/routes/todos/index.tsx
import type { LoaderFunction } from 'remix';
import { getAllTodos } from '~/api/todos';
type LoaderData = {
todos: Array<{id: number, title: string}>
}
export const loader: LoaderFunction = async () => {
const response = await getAllTodos();
const data: LoaderData = {
todos: response.data
};
return data;
}
The first line of the loader function is calling a function to get call todos, this function can be anything like RestAPI
endpoint or Graphql
endpoint, and then returning those todos wrapped inside an object.
That loader function will only run on server side and will never ship to client side. This shifts the whole mindset with which we used to create react applications, like data fetching on the client side using useEffect
and useEffect
Remix provides us a way to get that data returned from loader function to access in our react component via useLoaderData
hook.
// app/routes/todos/index.tsx
import { useLoaderData } from 'remix'
...
export default function TodoIndex() {
const { todos } = useLoaderData<LoaderData>();
return (
<div className="container">
<h2>My Todo's</h2>
<ul>
{todos.map(todo => (
<li key={todo.id}>
<a>{todo.title}</a>
</li>
))}
</ul>
</div>
)
}
Now let's create second route. So create app/routes/todos/$todoId.tsx
file. That's special file name to create dynamic route, in this file name todoId
will become the param we will use it to fetch the corresponding todo. Let's see it in action.
// app/routes/todos/$todoId.tsx
import { useLoaderData } from 'remix;
import type { LoaderFunction } from 'remix';
import { getTodoById } from '~/api/todos';
type Todo = {
id: number;
title: string;
description: string
};
type LoaderData = {
todo: Todo
};
export const loader: LoaderFunction = async ({ params }) => {
const response = await getTodoById(params.todoId);
const data: LoaderData = {
todo: response.data
};
return data;
}
export default function TodoRoute() {
const { todo } = useLoaderData<LoaderData>();
return (
<div className="container">
<p>{todo.title}</p>
<p>{todo.description}</p>
</div>
);
}
Quite similar to /todos
routes. Now let's add navigation from /todos
to /todos/${todoId}
. Edit app/routes/todos/index.tsx
file to add <Link/>
component from remix package.
// app/routes/todos/index.tsx
// import Link component from remix package.
import { useLoaderData, Link } from 'remix';
export default function TodoIndex() {
const { todos } = useLoaderData<LoaderData>();
return (
<div className="container">
<h2>My Todo's</h2>
<ul>
{todos.map(todo => (
<li key={todo.id}>
{/* replace <a> with Link tag */}
<Link to={todo.id.toString()}>{todo.title}</Link>
</li>
))}
</ul>
</div>
)
}
Now let's add a route to create a new todo.
Create app/routes/todos/new.tsx
file and add the following content.
// app/routes/todos/new.tsx
export default function NewTodo() {
return (
<div>
<form method="post">
<h1>Add New Todo</h1>
<div>
<label htmlFor="title">Title</label>
<input required type="text" id="title" name="title" />
</div>
<div>
<label htmlFor="description">Description</label>
<textarea required name="description" id="description" />
</div>
<button type="submit">Add Todo</button>
</form>
</div>
)
}
Remix takes the classic approach for doing mutations, to do any kind of mutation(create, update, delete) on the server you can use form
tag by specifying the method type. When the user submits the forms, browser serializes the form with input fields and send these fields to server which can be accessed via name attributes. Comparing this approach with what we are used to do like doing event.preventDefault
on form submission and then calling the api with state managed via useState
, is much simple and it works with just html no JavaScript is required.
To process this on the server we need to export action function from our route file. Like
// app/routes/todos/new.tsx
import { redirect } from 'remix'
import type { ActionFunction } from 'remix';
import { createTodo } from '~/api/todos';
export const action: ActionFunction = async ({ request }) => {
const formData = await request.formData();
const title = formData.get('title');
const description = formData.get('description');
const response = await createTodo({ title, description });
return redirect(`/todos/${response.data.id}`);
}
request.formData()
is being used to get access to request body, we are referencing to form fields via their name attribute.
I'm using a createTodo
function which will call the backend API to create todo in the database and return back the response object, again we can use any technique for persistent storage like RestAPI, GraphQL etc.
Here redirect will create a response object with 302
status code and given path which will navigate to newly created todo.
Let's now add a Link
component in app/routes/todos/index.tsx
for navigation.
// app/routes/todos/index.tsx
export default function TodoIndex() {
const { todos } = useLoaderData<LoaderData>();
return (
<div className="container">
<h2>My Todo's</h2>
<ul>
{todos.map(todo => (
<li key={todo.id}>
<Link to={todo.id.toString()}>{todo.title}</Link>
</li>
))}
</ul>
{/* Link component to navigate to new todo screen */}
<Link to="new" className="button">Add New Todo</Link>
</div>
)
}
Enable JavaScript
Till now we have used react with remix to build our app, but if you go to network tab and enable JS filter you will be surprised we are downloading ZERO JavaScript in our app. So our app will continue to work if for some reason, JavaScript on the client's browser fails to load or takes too much time to load because of slow internet connection.

But we want to enable JavaScript in our site to enhance user experience and also because some of the features can't be done without JavaScript. So let's do that, it's really simple, open app/root.tsx
file, import Scripts
component from remix and update the Document
component as follow
// app/root.tsx
function Document({
children,
title
}: {
children: React.ReactNode;
title?: string;
}) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
{title ? <title>{title}</title> : null}
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
{process.env.NODE_ENV === "development" && <LiveReload />}
</body>
</html>
);
}
With JavaScript enabled we can prefect resources, remix provides us very simple method for doing that. Let's update the app/routes/todos/index.tsx
file to update the Link
component to enable prefetching, for that just add prefetch="intent"
// app/routes/todos/index.tsx
export default function TodoIndex() {
const { todos } = useLoaderData<LoaderData>();
return (
<div className="container">
<h2>My Todo's</h2>
<ul>
{todos.map(todo => (
<li key={todo.id}>
{/* replaced with <a> tag */}
<Link prefetch="intent" to={todo.id.toString()}>{todo.title}</Link>
</li>
))}
</ul>
<Link to="new" className="button">Add New Todo</Link>
</div>
)
}
Now if the user hove over the todo link, remix will prefetch the required resources and keep them in browser cache and use it when needed. Super cool!
Libraries Support
For now remix is supporting only react, but the remix is designed in such a way that it can be used with other popular libraries like Vuejs. If we look at the code repository of the remix, we can see that
