Skip to main content

Overview

The Resolid Framework provides flexible file-based routing through the flexRoutes function, which generates React Router route configuration from your file system structure.

Function: flexRoutes()

Generates route configuration from a file system directory.
async function flexRoutes(
  options?: FolderRoutesOptions
): Promise<RouteConfigEntry[]>
options
FolderRoutesOptions
Configuration options for route generation
options.routesDirectory
string
default:"'routes'"
Directory containing route files relative to the app directory
options.ignoredRouteFiles
string[]
default:"[]"
Glob patterns for files to ignore when generating routes
return
Promise<RouteConfigEntry[]>
Array of route configuration entries for React Router

Basic Usage

In your react-router.config.ts:
import { defineDevConfig } from '@resolid/dev';
import { flexRoutes } from '@resolid/dev/routes';
import type { Config } from '@react-router/dev/config';

export default defineDevConfig({
  reactRouterConfig: {
    routes: await flexRoutes()
  }
}) satisfies Config;

Route File Conventions

The following file extensions are recognized as route modules:
  • .js
  • .jsx
  • .ts
  • .tsx
  • .md
  • .mdx

File-Based Routing Patterns

Basic Routes

app/routes/
├── _index.tsx          → /
├── about.tsx           → /about
├── contact.tsx         → /contact
└── pricing.tsx         → /pricing

Nested Routes

app/routes/
├── blog.tsx            → /blog (layout)
├── blog._index.tsx     → /blog (index)
├── blog.post.$id.tsx   → /blog/post/:id
└── blog.category.$slug.tsx → /blog/category/:slug

Dynamic Segments

app/routes/
├── users.$id.tsx       → /users/:id
├── posts.$slug.tsx     → /posts/:slug
└── products.$category.$id.tsx → /products/:category/:id

Optional Segments

app/routes/
└── users.$id?.tsx      → /users/:id? (optional)

Splat Routes

app/routes/
└── docs.$.tsx          → /docs/* (catch-all)

Pathless Layouts

app/routes/
├── _auth.tsx           → Layout without path
├── _auth.login.tsx     → /login (uses _auth layout)
└── _auth.register.tsx  → /register (uses _auth layout)

Configuration Options

Custom Routes Directory

export default defineDevConfig({
  reactRouterConfig: {
    routes: await flexRoutes({
      routesDirectory: 'pages' // Use app/pages instead of app/routes
    })
  }
});

Ignore Files

Exclude specific files from route generation:
export default defineDevConfig({
  reactRouterConfig: {
    routes: await flexRoutes({
      ignoredRouteFiles: [
        '**/*.test.tsx',
        '**/*.stories.tsx',
        '**/components/**',
        '**/_*.tsx' // Ignore files starting with underscore except layouts
      ]
    })
  }
});

Type Definitions

FolderRoutesOptions

type FolderRoutesOptions = {
  routesDirectory?: string;
  ignoredRouteFiles?: string[];
}

RouteConfigEntry

type RouteConfigEntry = {
  path?: string;
  index?: boolean;
  caseSensitive?: boolean;
  id: string;
  file: string;
  children?: RouteConfigEntry[];
}

Route File Structure

Each route file can export:

Loader

import type { Route } from 'react-router';

export const loader = async ({ params, request }: Route.LoaderArgs) => {
  const post = await getPost(params.id);
  return { post };
};

Action

export const action = async ({ request }: Route.ActionArgs) => {
  const formData = await request.formData();
  await createPost(formData);
  return redirect('/blog');
};

Component

import { useLoaderData } from 'react-router';

export default function BlogPost() {
  const { post } = useLoaderData<typeof loader>();
  return <article>{post.title}</article>;
}

Meta

import { mergeMeta } from '@resolid/dev/router';

export const meta = mergeMeta(({ data }) => [
  { title: data.post.title },
  { name: 'description', content: data.post.excerpt }
]);

Headers

export const headers = () => ({
  'Cache-Control': 'public, max-age=3600'
});
export const links = () => [
  { rel: 'stylesheet', href: '/styles/blog.css' }
];

Complete Example

Project Structure

app/
├── routes/
│   ├── _index.tsx
│   ├── about.tsx
│   ├── _auth.tsx
│   ├── _auth.login.tsx
│   ├── _auth.register.tsx
│   ├── blog.tsx
│   ├── blog._index.tsx
│   ├── blog.post.$id.tsx
│   ├── blog.category.$slug.tsx
│   ├── docs.$.tsx
│   └── api.users.$id.tsx
└── root.tsx

Configuration

// react-router.config.ts
import { defineDevConfig } from '@resolid/dev';
import { flexRoutes } from '@resolid/dev/routes';
import type { Config } from '@react-router/dev/config';

export default defineDevConfig({
  appDirectory: 'app',
  reactRouterConfig: {
    routes: await flexRoutes({
      routesDirectory: 'routes',
      ignoredRouteFiles: [
        '**/*.test.tsx',
        '**/components/**'
      ]
    })
  }
}) satisfies Config;

Example Route Files

// app/routes/_index.tsx
import { mergeMeta } from '@resolid/dev/router';

export const meta = mergeMeta(() => [
  { title: 'Home' },
  { name: 'description', content: 'Welcome to our site' }
]);

export default function Index() {
  return <h1>Home Page</h1>;
}
// app/routes/blog.tsx
import { Outlet } from 'react-router';

export default function BlogLayout() {
  return (
    <div>
      <nav>{/* Blog navigation */}</nav>
      <Outlet />
    </div>
  );
}
// app/routes/blog.post.$id.tsx
import { useLoaderData } from 'react-router';
import { mergeMeta } from '@resolid/dev/router';
import type { Route } from 'react-router';

export const loader = async ({ params }: Route.LoaderArgs) => {
  const post = await getPost(params.id);
  return { post };
};

export const meta = mergeMeta(({ data }) => [
  { title: data.post.title },
  { name: 'description', content: data.post.excerpt }
]);

export default function BlogPost() {
  const { post } = useLoaderData<typeof loader>();
  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
    </article>
  );
}
// app/routes/_auth.tsx
import { Outlet } from 'react-router';

export default function AuthLayout() {
  return (
    <div className="auth-layout">
      <Outlet />
    </div>
  );
}
// app/routes/_auth.login.tsx
import { Form } from 'react-router';
import type { Route } from 'react-router';

export const action = async ({ request }: Route.ActionArgs) => {
  const formData = await request.formData();
  await login(formData);
  return redirect('/dashboard');
};

export default function Login() {
  return (
    <Form method="post">
      <input type="email" name="email" />
      <input type="password" name="password" />
      <button type="submit">Login</button>
    </Form>
  );
}

Advanced Patterns

API Routes

Create API endpoints that return JSON:
// app/routes/api.users.$id.tsx
import type { Route } from 'react-router';

export const loader = async ({ params }: Route.LoaderArgs) => {
  const user = await getUser(params.id);
  return Response.json(user);
};

Markdown Routes

Use .mdx files for content-driven routes:
<!-- app/routes/blog.post.hello-world.mdx -->
---
meta:
  - title: Hello World
  - name: description
    content: My first blog post
---

# Hello World

Welcome to my blog!

Source Code

Location: packages/dev/src/routes/flex/index.ts

Build docs developers (and LLMs) love