DEV Community

Alex Spinov
Alex Spinov

Posted on

Remix v2 Has a Free Full-Stack Framework: Web Standards First, Nested Routing, and Progressive Enhancement

Next.js App Router introduced server components, cache layers, and use client boundaries. Your mental model now includes RSC payloads, parallel routes, intercepting routes, and cache revalidation strategies.

What if full-stack React just used web standards? Forms, HTTP, cookies, headers — the stuff browsers already understand?

That's Remix v2. Web platform first, framework magic second.

What Remix v2 Gives You

  • Web standardsRequest, Response, FormData, Headers — no proprietary APIs
  • Nested routing with parallel data loading — parent and child routes load data simultaneously
  • Progressive enhancement — forms work without JavaScript, then enhance with JS
  • Error boundaries per route — errors are isolated, not page-killing
  • Built-in optimistic UIuseNavigation, useFetcher for instant feedback
  • Vite-powered — fast builds, HMR, standard plugin ecosystem

Quick Start

npx create-remix@latest my-app
cd my-app && npm run dev
Enter fullscreen mode Exit fullscreen mode

Loader + Action Pattern — The Core of Remix

// app/routes/contacts.$contactId.tsx
import type { LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import { useLoaderData, Form } from "@remix-run/react";

// GET — load data (runs on server)
export async function loader({ params }: LoaderFunctionArgs) {
  const contact = await db.contact.findUnique({ where: { id: params.contactId } });
  if (!contact) throw new Response("Not Found", { status: 404 });
  return json({ contact });
}

// POST — handle mutations (runs on server)
export async function action({ request, params }: ActionFunctionArgs) {
  const formData = await request.formData();
  await db.contact.update({
    where: { id: params.contactId },
    data: { name: formData.get("name") as string },
  });
  return json({ ok: true });
}

export default function Contact() {
  const { contact } = useLoaderData<typeof loader>();

  return (
    <Form method="post">
      <input name="name" defaultValue={contact.name} />
      <button type="submit">Save</button>
    </Form>
  );
}
Enter fullscreen mode Exit fullscreen mode

This form works without JavaScript. The browser submits a POST, Remix runs the action, reloads the loader, and sends fresh HTML. With JS enabled, it intercepts the submit and does it seamlessly.

Nested Routing — Parallel Data Loading

app/routes/
  _layout.tsx          ← root layout (nav, sidebar)
  dashboard.tsx        ← /dashboard layout
  dashboard._index.tsx ← /dashboard (main content)
  dashboard.stats.tsx  ← /dashboard/stats
  dashboard.users.tsx  ← /dashboard/users
Enter fullscreen mode Exit fullscreen mode

When navigating to /dashboard/stats:

  1. _layout loader, dashboard loader, and dashboard.stats loader run in parallel
  2. Only the changed route segment re-renders
  3. Parent layout stays mounted — no layout shift

Optimistic UI with useFetcher

function TodoItem({ todo }) {
  const fetcher = useFetcher();

  // Show optimistic state immediately
  const isComplete = fetcher.formData 
    ? fetcher.formData.get("complete") === "true"
    : todo.complete;

  return (
    <fetcher.Form method="post" action={`/todos/${todo.id}`}>
      <input type="hidden" name="complete" value={(!isComplete).toString()} />
      <button>
        {isComplete ? "" : ""} {todo.title}
      </button>
    </fetcher.Form>
  );
}
Enter fullscreen mode Exit fullscreen mode

The checkbox toggles instantly — no loading spinner. If the server request fails, it reverts automatically.

Error Boundaries Per Route

// app/routes/dashboard.stats.tsx
export function ErrorBoundary() {
  const error = useRouteError();
  return <div>Stats failed to load. The rest of the dashboard still works.</div>;
}
Enter fullscreen mode Exit fullscreen mode

If the stats route throws, only the stats panel shows an error. The dashboard layout, navigation, and other panels keep working.

When to Choose Remix v2

Choose Remix when:

  • You want React without fighting the web platform
  • Forms and mutations are a big part of your app
  • Progressive enhancement matters (gov sites, accessibility)
  • You want simpler mental model than Next.js App Router

Skip Remix when:

  • You need static site generation for 10K+ pages
  • Your team is already productive with Next.js
  • You need React Server Components

The Bottom Line

Remix doesn't add abstractions on top of the web — it removes them. The result is code that's simpler to write, debug, and maintain.

Start here: remix.run/docs


Need custom data extraction, scraping, or automation? I build tools that collect and process data at scale — 78 actors on Apify Store and 265+ open-source repos. Email me: Spinov001@gmail.com | My Apify Actors

Top comments (0)