Cursor rule
.cursor/rules/20-hermes-app-routes.mdcCursor rules
Quality
65/100
Scores the file, not the repository.Length
742 words
23 headings · 17 code blocksRepository
45
— · pushed 3 days agoLast changed
3 days ago
First indexed 3 days ago.123456# Hermes App - TanStack Router Rules78## Route File Structure910### File-Based Routing11- Route files in `src/routes/`12- File names determine route paths using dot notation13- Special file names: `__root.tsx`, `index.tsx`1415```16routes/17├── __root.tsx # Root layout (wraps all routes)18├── index.tsx # Home page (/)19├── queue.tsx # /queue20├── settings.tsx # /settings21├── auth.login.tsx # /auth/login22└── auth.signup.tsx # /auth/signup23```2425### Route File Template26```typescript27import { createFileRoute } from "@tanstack/react-router";2829export const Route = createFileRoute("/route-path")({30 component: RouteComponent,31 loader: async ({ context, params }) => {32 // Load data33 return { data };34 },35 errorComponent: ErrorComponent,36 pendingComponent: LoadingComponent,37 beforeLoad: async ({ context, location }) => {38 // Auth checks, redirects39 },40});4142function RouteComponent() {43 const data = Route.useLoaderData();44 return <div>{/* Route content */}</div>;45}46```4748## Root Layout (__root.tsx)4950```typescript51import { createRootRoute, Outlet } from "@tanstack/react-router";52import { AppLayout } from "@/components/layout/AppLayout";5354export const Route = createRootRoute({55 component: RootLayout,56 errorComponent: RootErrorComponent,57});5859function RootLayout() {60 return (61 <AppLayout>62 <Outlet />63 </AppLayout>64 );65}66```6768## Data Loading6970### Loaders71```typescript72export const Route = createFileRoute("/downloads")({73 loader: async ({ context }) => {74 const downloads = await fetchDownloads();75 const stats = await fetchStats();76 return { downloads, stats };77 },78 component: DownloadsPage,79});8081function DownloadsPage() {82 const { downloads, stats } = Route.useLoaderData();83 return (84 <div>85 <Stats data={stats} />86 <DownloadList items={downloads} />87 </div>88 );89}90```9192### With Search Params93```typescript94export const Route = createFileRoute("/downloads")({95 validateSearch: (search: Record<string, unknown>) => {96 return {97 status: (search.status as string) || "all",98 page: Number(search.page) || 1,99 };100 },101 loader: async ({ context, search }) => {102 const downloads = await fetchDownloads({103 status: search.status,104 page: search.page,105 });106 return { downloads };107 },108});109```110111## Navigation112113### Link Component114```typescript115import { Link } from "@tanstack/react-router";116117<Link to="/queue" className="nav-link" activeProps={{ className: "active" }}>118 Queue119</Link>120121// With params122<Link to="/downloads/$downloadId" params={{ downloadId: "123" }}>123 View Download124</Link>125126// With search127<Link to="/downloads" search={{ status: "completed", page: 1 }}>128 Completed129</Link>130```131132### Programmatic Navigation133```typescript134import { useNavigate } from "@tanstack/react-router";135136function MyComponent() {137 const navigate = useNavigate();138139 const handleClick = () => {140 navigate({ to: "/downloads", search: { status: "active" } });141 };142143 return <button onClick={handleClick}>Go</button>;144}145```146147## Protected Routes148149```typescript150import { createFileRoute, redirect } from "@tanstack/react-router";151import { isAuthenticated } from "@/services/auth";152153export const Route = createFileRoute("/protected")({154 beforeLoad: async ({ context, location }) => {155 if (!isAuthenticated()) {156 throw redirect({157 to: "/auth/login",158 search: { redirect: location.href },159 });160 }161 },162 component: ProtectedPage,163});164```165166## Search Params167168### Validating Search169```typescript170import { z } from "zod";171172const searchSchema = z.object({173 status: z.enum(["all", "active", "completed", "failed"]).default("all"),174 sort: z.enum(["date", "title", "size"]).default("date"),175 page: z.number().int().positive().default(1),176 limit: z.number().int().positive().max(100).default(20),177});178179export const Route = createFileRoute("/downloads")({180 validateSearch: (search) => searchSchema.parse(search),181});182183function Component() {184 const search = Route.useSearch();185 // search is fully typed186}187```188189### Updating Search190```typescript191const navigate = useNavigate();192const search = Route.useSearch();193194const updateStatus = (status: string) => {195 navigate({196 search: (prev) => ({197 ...prev,198 status,199 page: 1, // Reset page200 }),201 });202};203```204205## Route Params206207```typescript208// File: routes/downloads.$downloadId.tsx209export const Route = createFileRoute("/downloads/$downloadId")({210 loader: async ({ params }) => {211 const download = await fetchDownload(params.downloadId);212 return { download };213 },214 component: DownloadDetail,215});216217function DownloadDetail() {218 const { download } = Route.useLoaderData();219 const { downloadId } = Route.useParams();220 return <div>{download.title}</div>;221}222```223224## Error Handling225226```typescript227export const Route = createFileRoute("/route")({228 errorComponent: RouteErrorComponent,229});230231function RouteErrorComponent({ error }: { error: Error }) {232 return (233 <div className="error-container">234 <h1>Something went wrong</h1>235 <p>{error.message}</p>236 </div>237 );238}239```240241### Loader Errors242```typescript243export const Route = createFileRoute("/data")({244 loader: async () => {245 const data = await fetchData();246 if (!data) throw new Error("Data not found");247 return { data };248 },249});250```251252## Loading States253254```typescript255export const Route = createFileRoute("/downloads")({256 loader: fetchDownloads,257 pendingComponent: LoadingDownloads,258 component: DownloadsPage,259});260261function LoadingDownloads() {262 return <div>Loading...</div>;263}264```265266## Route Context267268### Providing Context269```typescript270// In root route271export const Route = createRootRoute({272 beforeLoad: async () => {273 const user = await getCurrentUser();274 const config = await getConfig();275 return { auth: { user }, config };276 },277});278```279280### Consuming Context281```typescript282export const Route = createFileRoute("/profile")({283 beforeLoad: ({ context }) => {284 const user = context.auth?.user;285 return { user };286 },287});288```289290## Code Splitting291292```typescript293import { createFileRoute, lazyRouteComponent } from "@tanstack/react-router";294295export const Route = createFileRoute("/large")({296 component: lazyRouteComponent(() => import("./LargeComponent")),297});298```299300
Also in TechSquidTV/Hermes
Diff this repo’s formatsOne repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| TechSquidTV/Hermes.cursor/rules/00-project.mdc · 45 | Cursor rules | setuplint-formatstylearch+4 | 89/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/10-hermes-api.mdc · 45 | Cursor rules | testlint-formatstylearch+5 | 100/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/10-hermes-app.mdc · 45 | Cursor rules | lint-formatstylearchtypes+5 | 88/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/20-hermes-api-api.mdc · 45 | Cursor rules | stylearchdependenciesapi+2 | 77/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/20-hermes-api-db.mdc · 45 | Cursor rules | teststylearchtesting-strategy+3 | 73/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/20-hermes-api-tests.mdc · 45 | Cursor rules | teststyletesting-strategysecurity+3 | 97/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/20-hermes-app-components.mdc · 45 | Cursor rules | archtypesuiperformance+1 | 65/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/20-hermes-app-hooks.mdc · 45 | Cursor rules | lint-formatstylearchtypes+3 | 73/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/30-docker.mdc · 45 | Cursor rules | setupbuildstylesecurity+4 | 84/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/30-docs.mdc · 45 | Cursor rules | setuplint-formatstylearch+3 | 81/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/30-tests.mdc · 45 | Cursor rules | buildteststylearch+4 | 85/100 | 3 days ago |
Diff against .cursor/rules/00-project.mdc Diff against .cursor/rules/10-hermes-api.mdc Diff against .cursor/rules/10-hermes-app.mdc Diff against .cursor/rules/20-hermes-api-api.mdc Diff against .cursor/rules/20-hermes-api-db.mdc Diff against .cursor/rules/20-hermes-api-tests.mdc Diff against .cursor/rules/20-hermes-app-components.mdc Diff against .cursor/rules/20-hermes-app-hooks.mdc Diff against .cursor/rules/30-docker.mdc Diff against .cursor/rules/30-docs.mdc Diff against .cursor/rules/30-tests.mdc
Similar configs
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| hiromaily/go-crypto-wallet.cursor/rules/typescript.mdc · 126 | Cursor rules | setupbuildtestlint-format+6 | 100/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/10-hermes-api.mdc · 45 | Cursor rules | testlint-formatstylearch+5 | 100/100 | 3 days ago | |
| markstev/mark-starter.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+6 | 99/100 | 3 days ago | |
| Allymahmoud/case-intake-platform.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 3 days ago | |
| dodgecfr/combatfilms-webapp.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 3 days ago | |
| deifos/clipmira-subtitles.cursor/rules/frontend.mdc · 1 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 3 days ago | |
| langflow-ai/langflow.cursor/rules/docs_development.mdc · 153k | Cursor rules | setupbuildtestlint-format+7 | 97/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/20-hermes-api-tests.mdc · 45 | Cursor rules | teststyletesting-strategysecurity+3 | 97/100 | 3 days ago |
