Skip to content
Development
Skill

/remix-v2-routing

Remix v2 routing patterns. Use when implementing flat-routes v2 conventions, route file naming, nested layouts, resource routes, or root.tsx scaffolding. Triggers on _<name>.tsx (pathless layout), _index.tsx, $param, app/routes/, @remix-run/dev, defineRoutes, <Outlet /> in route

From plugin
beagle
82139 skills2 commands
Install
$ npx -y skills add existential-birds/beagle --skill remix-v2-routing --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/remix-v2-routing

Context preview

The summary Claude sees to decide when to auto-load this skill.

Remix v2 routing patterns. Use when implementing flat-routes v2 conventions, route file naming, nested layouts, resource routes, or root.tsx scaffolding. Triggers on _<name>.tsx (pathless layout), _index.tsx, $param, app/routes/, @remix-run/dev, defineRoutes, <Outlet /> in route

SKILL.md

remix-v2-routing.SKILL.md
name: remix-v2-routing
description: Remix v2 routing patterns. Use when implementing flat-routes v2 conventions, route file naming, nested layouts, resource routes, or root.tsx scaffolding. Triggers on _<name>.tsx (pathless layout), _index.tsx, $param, app/routes/, @remix-run/dev, defineRoutes, <Outlet /> in route modules.

Remix v2 Routing

Quick Reference

**Flat-routes v2 filename rules** (all files live in `app/routes/`):

_index.tsx                 → /
concerts.tsx               → /concerts                (acts as layout when dotted children exist; otherwise leaf for /concerts)
concerts._index.tsx        → /concerts                (renders under layout)
concerts.$city.tsx         → /concerts/:city          params.city
concerts.trending.tsx      → /concerts/trending
_auth.tsx + _auth.login.tsx → /login (pathless layout, no URL segment)
files.$.tsx                → /files/*                 params["*"]
($lang)._index.tsx         → / and /en (or /fr etc.) — optional segment
sitemap[.]xml.tsx          → /sitemap.xml             (escape literal)
concerts_.mine.tsx         → /concerts/mine           (opts out of layout)
dashboard/route.tsx        → /dashboard               (folder + route.tsx)
reports.$id[.pdf].tsx      → /reports/:id.pdf         (no default export = resource)

**Imports — always use `@remix-run/react`, never `react-router-dom`**:

import { Outlet, Link, useLoaderData, useParams } from "@remix-run/react";
import type { LoaderFunctionArgs } from "@remix-run/node"; // or /cloudflare, /deno

File Naming Conventions

Dots in filenames create URL slashes and parent/child nesting. Underscore prefix marks pathless segments (`_auth.tsx`) and index routes (`_index.tsx`). Trailing underscore (`concerts_.mine.tsx`) opts out of layout nesting while keeping the URL nested. Brackets escape literal characters: `sitemap[.]xml.tsx`. Splat is the single dollar sign: `$.tsx` exposes the rest of the path under `params["*"]`. Optional segments are wrapped in parens: `($lang)`.

See [references/conventions.md](references/conventions.md) for the full table and edge cases.

Nested Layouts

A parent module (`concerts.tsx`) renders `<Outlet />`; child routes (`concerts.$city.tsx`, `concerts._index.tsx`) mount inside it automatically based on the dot-delimited filename.

// app/routes/concerts.tsx
import { Outlet } from "@remix-run/react";

export default function ConcertsLayout() {
  return (
    <section>
      <nav>{/* concerts subnav */}</nav>
      <Outlet />
    </section>
  );
}
// app/routes/concerts._index.tsx  (renders at exactly /concerts)
export default function ConcertsIndex() {
  return <h1>Browse concerts</h1>;
}

For a layout with **no** URL contribution, prefix with a single underscore:

// app/routes/_auth.tsx          → wraps /login, /signup; no URL segment
// app/routes/_auth.login.tsx    → /login   (inherits _auth layout)
// app/routes/_auth.signup.tsx   → /signup

Dynamic Segments and Splats

`$name` captures a single segment; `$.tsx` captures the rest of the path. Loader receives values via `params`:

// app/routes/concerts.$city.tsx
import type { LoaderFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";

export async function loader({ params }: LoaderFunctionArgs) {
  if (!params.city) throw new Response("Not found", { status: 404 });
  return json({ city: params.city });
}

export default function City() {
  const { city } = useLoaderData<typeof loader>();
  return <h1>{city}</h1>;
}

Splat values live under `"*"` — there is no `params.splat`:

// app/routes/files.$.tsx
export async function loader({ params }: LoaderFunctionArgs) {
  const rest = params["*"]; // bracket access only
  return new Response(await readBlob(rest), { headers: { "Content-Type": "application/octet-stream" } });
}

Root Module

`app/root.tsx` is the only required route. It owns the document shell and must render `<Meta />`, `<Links />`, `<Outlet />`, `<ScrollRestoration />`, `<Scripts />`, and (during dev) `<LiveReload />`. See [references/root.md](references/root.md).

Resource Routes

A route module without a `default` export is a **resource route** — it returns raw `Response` objects (PDF, JSON, RSS, webhooks). Parent loaders do **not** run, and `<Link>` must use `reloadDocument` (or be replaced with `<a>`) to trigger a real document request. See [references/resource-routes.md](references/resource-routes.md).

Gates (decision sequencing)

Answer **in order**. **Pass** means the condition is true; pick the answer on the same line and **stop**.

Layout vs flat URL

0. **Is there shared chrome at all (nav, breadcrumbs, sidebar) at this level**?

  • **Fail →** Use plain dotted segments (`about.tsx`, `pricing.tsx`); no layout module needed. **Stop.**
  • **Pass →** Step 1.

1. **Should this URL share UI (nav, breadcrumbs, sidebar) with a parent path**?

  • **Pass →** Use dot-delimited nesting (`concerts.$city.tsx` under `concerts.tsx`). **Stop.**
  • **Fail →** Step 2.

2. **Does the URL just *happen* to be nested but should render standalone**?

  • **Pass →** Trailing underscore (`concerts_.mine.tsx`). **Stop.**
  • **Fail →** Step 3.

3. **Need a wrapper layout but no parent URL segment**?

  • **Pass →** Single-underscore pathless parent (`_auth.tsx` + `_auth.login.tsx`). **Stop.**

UI route vs resource route

1. **Does this URL ever render HTML to a user**?

  • **Pass →** Export a `default` component. UI route. **Stop.**
  • **Fail →** Step 2.

2. **Returns JSON, PDF, RSS, sitemap, webhook, or other raw `Response`**?

  • **Pass →** Omit `default` export — module becomes a resource route. Use `reloadDocument` on any `<Link>` pointing to it. **Stop.**

`_index.tsx` vs `index.tsx`

1. **On Remix v2 with flat-routes**?

  • **Pass →** `_index.tsx` (leading underscore). **Stop.**
  • **Fail →** Step 2 —
Read more
Ships withbeagle

Image: NASA, Public Domain. Source Beagle is an Agent Skills marketplace: framework-aware code review, documentation, testing, architectural analysis, and git workflows for any compatible coding agent.

Get the whole plugin

Other skills on beagle.