Introduction to Remix
By Flavio Copes
An introduction to Remix, now React Router framework mode: what it is, how to scaffold an app with create-react-router, and how to load data with loader().
With this post I want to help you get started with Remix with my usual 80/20 approach: skip the fluff, learn the core.
What’s Remix? It’s a React-based framework.
One important update since I first wrote this post. Remix v2 was folded into React Router 7 as “framework mode”, and React Router is at version 8 as of September 2026. Remix v2 (@remix-run/react 2.17.x) is in maintenance mode. If you run npx create-remix@latest today it does not scaffold anything: it prints a message telling you to use npx create-react-router@latest instead, so that’s the command we’ll use here.
There is also a Remix 3 in the works, at release candidate stage. It’s a different project: a full-stack framework built on Web APIs with its own UI layer, not based on React Router. This post is not about that.
Do you know Next.js? Or SvelteKit? Well, Remix (now React Router as a framework) is something like that, but with some unique features that make it an interesting alternative. It renders on the server by default. When I first wrote this, Remix had no static output at all. React Router framework mode now has a SPA mode (ssr: false in react-router.config.ts) and prerendering, but the server-rendered, database-backed app is still where it shines.
Which makes it good for some use cases, less so for others.
Good for use cases where you have a database, dynamic data, user accounts with private data, and so on. Like with a Rails, Django or Laravel app.
We create a new project using npx, so we don’t have to install anything or even create a folder up front.
Just go in the folder that contains your projects (it’s usually dev or www for me) and type
npx create-react-router@latest
create-react-router is on the 8 line as of September 2026, same as React Router itself.
The installer asks a few questions: the folder to create the app in, whether to initialize a Git repository, whether to install dependencies, and whether to add the React Router agent skill. Then you’re ready to run the app.
Remix v2 also asked for a deployment target like Remix App Server. The React Router installer skips that menu. The default template runs on Node with @react-router/serve.
Remix v2 asked to pick TypeScript or JavaScript. The current default template is TypeScript and there is no prompt for it.
Make sure dependencies get installed (npm install if the CLI didn’t already).
Then cd <foldername> and run npm run dev.
The dev server is Vite now, so the app runs at http://localhost:5173 (Remix v2 used port 3000). Open it in the browser to see the default welcome page.
Now open the project in your editor. The starter layout changed over time, but the app folder is still the one you care about. The rest is boilerplate and configuration.
In the app folder you’ll typically find things like:
root.tsxfor the overall HTML shell- a
routesfolder for pages, plus aroutes.tsfile that lists them (React Router uses explicit route config by default) - optional
entry.client/entry.serverfiles for the client/server bootstrap
Those root/entry files set the overall site functionality, including the HTML output for all pages. They’re the ones that if you touch them, the entire app will be affected. And that’s where error handling happens, which is a first-class aspect here, which is kind of cool.
Let’s look at a home route. In the default template it’s app/routes/home.tsx (Remix v2 used app/routes/_index.tsx). The interesting bit is the same: a React component plus a server loader().
A trimmed-down version looks like this:
import { useLoaderData, Link } from "react-router";
// Loaders provide data to components and are only ever called on the server, so
// you can connect to a database or run any server side code you want right next
// to the component that renders it.
export async function loader() {
return {
resources: [
{
name: "React Router Docs",
url: "https://reactrouter.com/docs"
},
{
name: "Remix Docs",
url: "https://remix.run/docs"
}
]
};
}
export function meta() {
return [
{ title: "Welcome" },
{ name: "description", content: "Getting started" }
];
}
export default function Index() {
let data = useLoaderData();
return (
<div>
<h2>Welcome!</h2>
<ul>
{data.resources.map(resource => (
<li key={resource.url}>
<a href={resource.url}>{resource.name}</a>
</li>
))}
</ul>
<Link to="/about">About</Link>
</div>
);
}
Imports come from "react-router" now. Remix v2 code used @remix-run/react and @remix-run/node, and very old tutorials imported from "remix".
Now, this file could be simplified down to a super simple React component like this:
export default function() {
return (
<div>
Test
</div>
);
}
and if you do so and save the file, the page just shows “Test”.
But the interesting part is loading data on the server-side, via the loader() function:
export async function loader() {
return {
//.... some data
};
}
You return a plain object (or a Response). Remix v2 wrapped it in a json() helper, which is gone now. The framework calls this loader when the page loads, to fill it with the data it needs.
This loader() function is called before rendering, and only runs server-side.
To use this data in the component’s JSX, we import and call useLoaderData():
import { useLoaderData } from "react-router"
export async function loader() {
return {
name: 'Flavio'
}
}
export default function() {
let data = useLoaderData()
return (
<div>
Hi {data.name}
</div>
)
}
The page now shows “Hi Flavio”.
The file can also export a meta() function, which is used to set the page HTML head’s meta data.
Routes are where the “meat” happens. Which is understandable since Remix was created by the people that made React Router, so the router is the center part of the app.
The official tutorial already talks about creating custom routes and handling forms, so I won’t go in more details about that now.
I suggest you to take a look at that, especially at forms.
Why? Because forms are a big pain in React, so it’s nice to see this very simple approach at allowing you to create forms without having to write lots and lots of boilerplate code.
I think that’s the nicest part of Remix and I’d consider using this if I want to have a site with first-class forms, to avoid using libraries that I need to learn from scratch and are another dependency to worry about.
Another interesting thing is child routes, and how that basically replicates what we used to do with Ember and its outlets in the pre-React days, and it makes sense since I remember Ryan Florence being active in the Ember community back in the day.
And it’s great to see progress in a field that has not seen any big incumbents, where Next.js is basically the elephant in the room and it’s the thing I’ve defaulted to so far when it comes to writing an app.
And I’ll still default to that for the time being. I like new tech, but it also takes a long time for it to become mature, have people write libraries, tutorials, etc etc.
But competition fosters innovation, so it’s always good to have an option that is peculiar enough to not be a copycat of existing alternatives.
Want me to talk about your product? You can sponsor this site.