client-side-routing

$npx mdskill add BuilderIO/agent-native/client-side-routing

Prevent chat reloads by mounting the app shell once above routes.

  • Preserves agent state and prevents unnecessary backend reconnections.
  • Requires React Router and single-page application architecture.
  • Decides layout placement by analyzing route protection and public access.
  • Delivers results by keeping persistent chrome mounted during navigation.
SKILL.md
.github/skills/client-side-routingView on GitHub ↗
---
name: client-side-routing
description: >-
  How to add routes without remounting the app shell. Use when adding a new
  route, fixing agent sidebar reloads on navigation, or choosing between
  `root.tsx` layout and pathless `_app.tsx` layout patterns.
---

# Client-Side Routing

## Rule

All templates are single-page apps using React Router. **Navigation must not remount the app shell.** The agent sidebar, document tree, and any other persistent chrome must survive route changes.

## Why

If the shell unmounts on every navigation, the agent chat reconnects/reloads, destroying in-progress work and hammering the backend.

## Hard Rule

**The app shell (AgentSidebar + any top-level navigation) must be mounted ONCE, above the `<Outlet />`.** Never wrap each page in its own `<AppLayout>` / `<Layout>`. React sees a different component at the outlet position on each nav and unmounts the entire subtree.

## Two Correct Patterns

### 1. All routes need the shell

Mount `<AppLayout>` in `root.tsx` around `<Outlet />`:

```tsx
// app/root.tsx
<AppLayout>
  <Outlet />
</AppLayout>
```

### 2. Mix of protected and public routes

Use a React Router **pathless layout route**:

```
app/routes/
  _app.tsx                  # renders <AppLayout><Outlet /></AppLayout>
  _app._index.tsx           # / → under AppLayout
  _app.settings.tsx         # /settings → under AppLayout
  _app.team.tsx             # /team → under AppLayout
  book.$slug.tsx            # /book/:slug → no layout (public)
  f.$.tsx                   # /f/* → no layout (public form filler)
```

The `_` prefix on `_app.tsx` makes it a **pathless** parent — it contributes the layout but no URL segment. Route files prefixed with `_app.` nest under it and share the layout instance across navigations.

## Anti-Pattern

```tsx
// ❌ BAD — each route wraps its own Layout, causing full remount on every nav
export default function Settings() {
  return (
    <AppLayout>
      <SettingsContent />
    </AppLayout>
  );
}
```

If a page needs per-route data (e.g. sidebar highlighting the active document), derive it inside the layout from `useParams()` / `useLocation()` — don't pass it as a prop through every route file.

## Adding a New Route

- **Pattern #1** (AppLayout in `root.tsx`): just render page content — nothing else.
- **Pattern #2** (pathless `_app.tsx`): name the file `_app.<segment>.tsx` for authed routes, or bare `<segment>.tsx` for public routes.

## Related Skills

- `adding-a-feature` — The four-area checklist referencing route layout patterns
- `context-awareness` — Navigation state written on every route change
More from BuilderIO/agent-native