Stop 10 · Full-Stack FrameworksTopics · 4/7

Remix (Overview)

Part of the Full Stack Developer Roadmap

In short

Remix bet on plain web basics: forms that work without JavaScript, data loaded per route, and pages that refresh themselves after a write. Those ideas now live on inside React Router v7.

A full-stack React framework emphasizing web fundamentals (nested routing, progressive enhancement, loaders/actions tightly coupled to routes) — a notable alternative to Next.js with a different philosophy.

The blob character stretches to hand tiffin boxes into three train coach windows at once.

Think of it like this

Every coach loads at once

At a busy station, tiffins for coaches A, B and C are loaded by different helpers at the same time, instead of one person walking the whole train. Remix's nested routes work like that: each part of the page has its own loader, and they all run in parallel.

Watch it happen

Loaders read, actions write

Browser
Remix server
Database

GET /invoices/7

1/7The URL matches two nested routes: the invoices layout and the invoice 7 page.

The big ideas

What you need to know

Loaders read

Each route exports a loader that runs on the server before rendering and returns that route's data.

Actions write

Each route can export an action that handles its own form posts. No separate API file needed.

Nested routes

Layouts and child pages each own their data, and their loaders run in parallel instead of one after another.

Works without JS

<Form> falls back to a real HTML form post, so the core flow still works if JavaScript fails to load.

See it in code

Walk through the code

tsx
1// app/routes/notes.tsx2import { Form } from "react-router";3import type { Route } from "./+types/notes";4 5export async function loader() {6  return { notes: await db.note.findMany() };7}8export async function action({ request }: Route.ActionArgs) {9  const form = await request.formData();10  await db.note.create({ data: { text: String(form.get("text")) } });11  return null;12}13export default function Notes({ loaderData }: Route.ComponentProps) {14  const list = loaderData.notes.map((n) => <li key={n.id}>{n.text}</li>);15  return <><ul>{list}</ul><Form method="post"><input name="text" /><button>Add</button></Form></>;16}

1/4The loader runs on the server for GET requests and returns this route's data.

This is React Router v7 framework mode, where Remix's ideas now live. The ./+types import is generated for you and types loaderData.

Save yourself the pain

Common mistakes

Oops

Fetching page data in useEffect inside a Remix route.

Do this instead

Put it in the route's loader. It runs on the server before render, so the HTML arrives with data.

Oops

Manually refetching the list after a form submit.

Do this instead

Return from the action and let the router revalidate loaders. The list updates by itself.

Your mission

A notes page the Remix way

~35 min

Quick check

After a Remix action saves a new note, how does the notes list update?

Go deeper

  1. 1Read Remix's loader/action model and compare it to Next.js's data-fetching approach.
  2. 2Understand Remix's emphasis on working well even with JavaScript disabled.
  3. 3Recognize the core full-stack concepts transfer between these frameworks even if the syntax differs.

More topics at this stop

Stuck on this topic? Ask an Insider

Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.

Download
InsideEdge