Skip to content
Back to skills

New Route Scaffold

ASecurity

Scaffold recipe for a new authenticated route: SSR shell, DB-derived types, Redux hydration, view sub-pages. Use when creating a new route or page, adding a feature with a sidebar/main layout, adding view modes to a route, or feature types don't match database.types.ts.

  • 3 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 3, 2026
developmenttypescriptgoshellbashreactnextjstestingdatabase

Works with

  • cli

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned October 3, 2026

npx -y skills add armanisadeghi/ai-matrx --skill new-route-scaffold --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of New Route Scaffold?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for New Route Scaffold
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/armanisadeghi-new-route-scaffold/badge)](https://www.skillsdirectory.com/skills/armanisadeghi-new-route-scaffold)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: new-route-scaffold
description: "Scaffold recipe for a new authenticated route: SSR shell, DB-derived types, Redux hydration, view sub-pages. Use when creating a new route or page, adding a feature with a sidebar/main layout, adding view modes to a route, or feature types don't match database.types.ts."
---

# New Route Scaffold — `app/(core)/`

This skill captures the exact workflow used to build the original `/notes` route (now `app/(core)/notes`, since rebuilt as a client workspace — see [route-architecture.md](route-architecture.md)). Follow it for every new authenticated route. **Many decisions require information only Arman has — ask explicitly before assuming anything.**

## Companion files — read at the step that needs them

- **Writing any scaffold file in Phases 1–4 → copy its template from [file-templates.md](file-templates.md).** Every code template (types, `data.ts`, route files, hydrators) lives there, keyed by phase.
- **Route UI not built yet (placeholder `page.tsx`) → read [coming-soon-placeholder.md](coming-soon-placeholder.md).**
- **Full shell implementations, data flow, anti-patterns, mobile, more questions for Arman → [route-architecture.md](route-architecture.md).**
- **A route needs agent-aware context or bindings → invoke `surface-authoring`; this includes agent-native routes, where outside helpers may receive an approved task-appropriate snapshot while native and resident agents remain isolated from ambient inheritance.**

## STOP: Questions to Ask Before Writing a Single Line

Never assume the following — always ask Arman:

1. **Layout shape** — How many panels? Sidebar width? Resizable? What goes in the header area?
2. **View modes** — Does this route have sub-views (like edit/preview/split)? What are they named?
3. **Group-by / filter dimensions** — How is the sidebar list organized? What FK relationships drive grouping?
4. **Data fetch scope** — What fields does the list view need? What triggers a "full" fetch?
5. **Redux slice** — Does one already exist? Should we create one or extend an existing slice?
6. **Existing types** — Do the feature types already exist? Are they derived from the DB or hand-written (likely stale)?
7. **Navigation entry** — Does `features/shell/constants/nav-data.ts` already have an entry? What favicon color/letter (`constants/favicon-route-data.ts`)?
8. **Mobile behavior** — Any mobile-specific overrides beyond standard rules?

**If anything is unclear, stop and ask. Do not invent layout details, field names, or group-by modes.**

---

## Phase 1: Audit & Fix Types

Before touching any route file, verify the feature type file matches `types/database.types.ts`.

### Step 1 — Find the DB Row shape

```bash
# Search for the table definition in the generated types
grep -A 30 '"your_table": {' types/database.types.ts
```

### Step 2 — Compare against the feature type file

Located at `features/[feature]/types.ts`. Hand-written types are almost always missing fields. The canonical pattern:

**Template → copy the canonical `features/[feature]/types.ts` pattern (aliases, working interface, `_CompatCheck` guard, list projection, route enums) from [file-templates.md](file-templates.md) § Phase 1.**

### Step 3 — Update Redux `notes.types.ts` factory functions

The `createBlankNoteRecordFromPartial` and `createAutogeneratedNoteRecord` functions hardcode default values for every field. After adding fields to the interface, update both factories to include the new fields — otherwise TypeScript will error. Key rule: **new nullable fields default to `null`, not `""` or `[]`.**

---

## Phase 2: Server Data Layer

Create `lib/[feature]/data.ts`. This is the **only** place server-side DB queries live for this route.

**Template → copy `lib/[feature]/data.ts` (list seed, single entity, preload helper) from [file-templates.md](file-templates.md) § Phase 2.**

**Critical rules:**
- `import "server-only"` is mandatory — it prevents accidental client import
- Every function must be wrapped in React `cache()` — layout, `generateMetadata`, and page all call the same function; `cache()` collapses them to one DB hit. **`'use cache'` is NOT available** (`cacheComponents` off; build error)
- 🚨 **Never `notFound()` on an empty single-record read** (`authInterrupts` is ON) — under RLS, empty means deleted, missing, denied, or signed out. The single-record read returns `null`; the route renders `<AccessGate token id/>` (live: `app/(core)/lists/[id]/page.tsx`) or refuses with `requireAccess(type, id, level, { forbid: true })`. Read `features/access-gate/FEATURE.md` first
- The preload pattern starts a fetch before an await chain, eliminating waterfalls

---

## Phase 3: Route Files

### File map

```
app/(core)/[feature]/
├── layout.tsx          ← static metadata only
├── loading.tsx         ← dimension-exact skeleton of the full shell
├── error.tsx           ← <ErrorBoundaryView context="[Feature]" /> one-liner
├── page.tsx            ← fetches list seed, hydrates Redux, renders shell
└── [id]/
    ├── layout.tsx      ← parallel fetch, generateMetadata, BOTH hydrators, shell
    ├── loading.tsx     ← skeleton of the content area only (not the full shell)
    ├── error.tsx       ← <ErrorBoundaryView context="[Feature] Detail" /> one-liner
    ├── not-found.tsx   ← malformed-URL 404 only; an empty record read renders <AccessGate>
    ├── page.tsx        ← redirect to default view
    ├── edit/layout.tsx ← titlePrefix sub-layout (Phase 6), one per view that needs its own tab title
    ├── edit/page.tsx
    ├── split/page.tsx
    ├── rich/page.tsx
    ├── md/page.tsx
    ├── preview/page.tsx
    └── diff/page.tsx
```

### `error.tsx` (route root and `[id]/`)

Every error boundary is a one-liner — **never build error UI inline**.

```typescript
"use client";
import { ErrorBoundaryView } from "@/components/errors/ErrorBoundaryView";

export default function MyFeatureError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
    return <ErrorBoundaryView error={error} reset={reset} context="My Feature" />;
}
```

Optional props: `context` (string label for console logs), `homePath` (override home button path, default `"/"`).

`ErrorBoundaryView` shows all users a polished error UI with retry/back/home actions. Admins get a collapsible debug panel with full error details, request context, user context, stack trace, raw JSON dump, and a **"Copy for AI"** button that strips minified chunk URLs and formats a clean Markdown summary for pasting into any AI chat.

---

### `page.tsx` — placeholder for routes under development

**Only when the route's real UI isn't built yet → read [coming-soon-placeholder.md](coming-soon-placeholder.md)** (`ComingSoonPage` usage and the four props you must always set).

---

### `layout.tsx` (route root)

Static metadata only. No data fetching, no children manipulation.

**Template → [file-templates.md](file-templates.md) § Phase 3.**

### `loading.tsx` (route root)

Must mirror the exact dimensions of the shell. Every element has explicit `h-` and `w-`. No content-derived sizing. See [route-architecture.md](route-architecture.md) for the full skeleton example from the notes route.

Key rules:
- Outer container: `h-full flex overflow-hidden` (AppShell `.shell-main` is already full viewport — do not subtract header height)
- Route chrome: `<PageHeader>` — never an in-body faux header bar
- Sidebar: `w-[280px] shrink-0` — must match the real aside exactly
- No spinners — `<Skeleton>` components only
- `[id]/loading.tsx` mirrors the content area only (layout persists across navigation)

### `page.tsx` (route root — index, no item selected)

**Template → [file-templates.md](file-templates.md) § Phase 3** (list seed + hydrator + `Suspense` + empty state).

### `[id]/layout.tsx` — the most important file

This is where both hydrators live and parallel fetching happens.

**Template → [file-templates.md](file-templates.md) § Phase 3** (`generateMetadata`, `preloadNote` + `Promise.all`, `<AccessGate>` on an empty read, both hydrators, shell).

### `[id]/page.tsx` — redirect to default view

**Template → [file-templates.md](file-templates.md) § Phase 3.**

### View sub-pages (`[id]/edit/page.tsx`, etc.)

Sub-pages export no metadata and fetch no data. A view that needs its own tab title gets a sibling `layout.tsx` with `titlePrefix` (Phase 6) — never a page-level `{ title }`.

**Templates → [file-templates.md](file-templates.md) § Phase 3** (the `titlePrefix` sub-layout, the single-panel view, and the split view that passes both panels).

---

## Phase 4: Redux Hydrators

Both live in `features/[feature]/route/`. They render `null` and dispatch synchronously during the first render pass — **not** in `useEffect`.

**Templates → copy `ListHydrator` + `EntityHydrator` from [file-templates.md](file-templates.md) § Phase 4.**

**Why `useRef` and NOT `useEffect`:**
`useEffect` fires after paint — children reading from the store see empty state for one frame and flash. The `useRef` guard dispatches during the render pass, before any child reads.

---

## Phase 5: Shell Components

All shell components are Server Components. Client Component islands are pushed as deep as possible — only the interactive parts get `"use client"`.

**Component tree:**
```
[Feature]Shell (Server) — h-full flex overflow-hidden (+ PageHeader for route chrome)
├── [Feature]Sidebar (Server — w-[NNpx] shrink-0 frame)         ← ask Arman for width
│   └── [Feature]SidebarClient (Client — Redux reads, interactions)
└── [Feature]MainArea (Server — flex-1 min-w-0 frame)
    ├── [Feature]TabBar (Client — open tabs from Redux)
    └── {children} per page → [Feature]ViewShell (Server)
        ├── leftPanel  (Client editor island)
        └── rightPanel (Client preview island — split mode only)
```

**Key layout rules:**
- Outermost: `h-full overflow-hidden` — never `h-screen`, `h-page`, or `calc(100dvh - header)` on `(core)` routes
- Route chrome: `<PageHeader>` — see `features/shell/components/header/variants/USAGE.md`
- Sidebar: `w-[280px] shrink-0` — ask Arman for the exact width
- Main area: `flex-1 min-w-0 overflow-hidden`
- Scroll regions: `overflow-y-auto` only on the inner list container
- No inline styles unless Arman has explicitly approved (e.g., glass mode testing)

See [route-architecture.md](route-architecture.md) for the `NotesShell` / `NoteViewShell` composition — the pattern to build, not importable code (notes deleted its `shell/` components 2026-06-24, `features/notes/FEATURE.md`).

---

## Phase 6: Metadata Rules

From `app/(core)/_read_first_route_rules/metadata-and-seo.md` (full recipe: the `route-metadata-favicons` skill):

| Location | What to use | Notes |
|---|---|---|
| `layout.tsx` (root) | `createRouteMetadata("/path", { title, description })` | Provides favicon + OG for entire route |
| `[id]/layout.tsx` | `createDynamicRouteMetadata("/path", { title, description })` inside `generateMetadata` | Provides favicon + OG for item detail |
| `[id]/<view>/layout.tsx` | `createDynamicRouteMetadata("/path", { titlePrefix: "View Name", title: entity.name, letter })` (static route: `createRouteMetadata` + `titlePrefix`) | Tab reads `View Name \| Entity — AI Matrx`. A page-level `{ title }` renders `View Name — AI Matrx` and loses the name. No distinct title → no metadata; the parent layout covers favicon/OG. Live: `agents/[id]/run/layout.tsx` |
| New route | Add favicon entry in `constants/favicon-route-data.ts` | Ask Arman for color and letter |

---

## Phase 7: Navigation Entry

Add to `features/shell/constants/nav-data.ts` (label, href, `iconName`, section, color) if not already present; the favicon hex + letter go in `constants/favicon-route-data.ts`. **Ask Arman for all values** — icon, href, section, color (Tailwind name + hex), and favicon letter. Never guess these.

---

## Skeleton Rules (Non-Negotiable)

Every `loading.tsx` must satisfy:

1. **Identical outer container** — same `h-`, `w-`, `flex` classes as the real shell
2. **Exact fixed dimensions** — every skeleton block has explicit `h-` and `w-` (no content-derived sizing)
3. **Mirror structure** — sidebar skeleton has toolbar row + list items; editor skeleton has title + body lines
4. **No spinners for page content** — `<Skeleton>` only (from `@ai-matrx/design-system`)
5. **`[id]/loading.tsx`** covers only the content area, not the full shell (the layout persists)

---

## Checklist Before Asking Arman to Review

- [ ] DB type audit done — `_CompatCheck` added to types file
- [ ] All factory functions updated with new fields
- [ ] `lib/[feature]/data.ts` created with `server-only`, `cache()`, and `preloadNote`
- [ ] `app/(core)/[feature]/layout.tsx` — static metadata only
- [ ] `app/(core)/[feature]/loading.tsx` — exact dimension match to shell
- [ ] `app/(core)/[feature]/error.tsx` — one-liner wrapping `<ErrorBoundaryView context="[Feature]" />`
- [ ] `app/(core)/[feature]/page.tsx` — list seed + both hydrators + Suspense
- [ ] `app/(core)/[feature]/[id]/layout.tsx` — parallel fetch + both hydrators + generateMetadata
- [ ] `app/(core)/[feature]/[id]/loading.tsx` — content-area skeleton only
- [ ] `app/(core)/[feature]/[id]/error.tsx` — one-liner wrapping `<ErrorBoundaryView context="[Feature] Detail" />`, plus `not-found.tsx` (malformed URLs only)
- [ ] Empty single-record read renders `<AccessGate token id/>` — no `notFound()` anywhere in `lib/[feature]/data.ts`
- [ ] `app/(core)/[feature]/[id]/page.tsx` — redirect to default view
- [ ] All view sub-pages created (ask Arman for the list)
- [ ] `features/[feature]/route/` — `ListHydrator` + `EntityHydrator`
- [ ] Shell components: `Shell`, `Sidebar`, `SidebarClient`, `MainArea`, `TabBar`, `ViewShell`, `EditorPlaceholder`
- [ ] `NoteEditorPlaceholder` (or equivalent) clearly marked as stub for later replacement
- [ ] Navigation entry added (or confirmed already present)
- [ ] No `useEffect` in hydrators
- [ ] No `h-screen` or `min-h-screen` anywhere — use `h-dvh` for marketing pages; `h-full` for `(core)` AppShell bodies
- [ ] No inline styles (unless Arman explicitly approves for testing)
- [ ] Lints pass on all new files

---

## Related Skills — Read These First

These skills contain the rules this workflow is built on. When in doubt, they win:

- **`ssr-zero-layout-shift`** — `.claude/skills/ssr-zero-layout-shift/SKILL.md` — server/client component boundaries, Suspense rules, hydration pattern, skeleton design, fixed-dimension containers, CLS prevention (absorbed `nextjs-ssr-architecture` + `nextjs-app-router-expert`)
- **Shell header + page height** — `features/shell/components/header/variants/USAGE.md` — `<PageHeader>`, `h-full`, when `.h-page` applies
- **`redux-selector-rules`** — `.claude/skills/redux-selector-rules/SKILL.md` — selector patterns, curried selector caching, avoiding re-render loops
- **`core-route-headers`** — `.claude/skills/core-route-headers/SKILL.md` — invoke before writing any `(core)` shell or `loading.tsx`: `<PageHeader>` chrome, `h-full overflow-hidden` body, mobile header
- **Route rules** — `app/(core)/_read_first_route_rules/RULES.md` — mandatory, read before every session on this route group

---

## Additional Resources

- Full architecture patterns and route composition examples: [route-architecture.md](route-architecture.md)

Files in this skill

  • SKILL.md15.3 KB
  • coming-soon-placeholder.md1.3 KB
  • file-templates.md13.7 KB
  • route-architecture.md8.4 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…