Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/reatom/reatom/llms.txt

Use this file to discover all available pages before exploring further.

Overview

reatomRoute creates a reactive route atom that automatically syncs with the browser URL. Routes provide:
  • URL pattern matching with parameters
  • Type-safe navigation
  • Schema validation for parameters
  • Async data loading with automatic abort
  • Component rendering with outlet composition
  • Hierarchical route nesting

Type Signature

function reatomRoute<
  Path extends string = '',
  ParamsInput = PathParams<Path>,
  SearchInput = {},
  ParamsOutput = ParamsInput,
  SearchOutput = SearchInput,
>(
  pathOrOptions: string | RouteOptions<Path, ParamsInput, SearchInput, ParamsOutput, SearchOutput>,
  name?: string
): RouteAtom<Path, ParamsOutput, SearchOutput>

Parameters

pathOrOptions
string | RouteOptions
required
Either a path pattern string (e.g., 'users/:userId') or a configuration object
name
string
Debug name for the route atom

Return Value

RouteAtom
RouteAtom<Path, ParamsOutput, SearchOutput>
Route atom that returns validated params when matched, or null when not matched.

Examples

Basic Routes

import { reatomRoute } from '@reatom/framework'

const homeRoute = reatomRoute('')
const aboutRoute = reatomRoute('about')
const userRoute = reatomRoute('users/:userId')

// Navigate
homeRoute.go()
aboutRoute.go()
userRoute.go({ userId: '123' })

// Check if matched
console.log(homeRoute()) // {} when at /
console.log(userRoute()) // { userId: '123' } when at /users/123
console.log(userRoute()) // null when not matched

// Build URLs
console.log(aboutRoute.path()) // '/about'
console.log(userRoute.path({ userId: '456' })) // '/users/456'

Optional Parameters

import { reatomRoute } from '@reatom/framework'

const postRoute = reatomRoute('posts/:postId?')

// Both paths match
postRoute.go() // /posts
postRoute.go({ postId: 'abc' }) // /posts/abc

console.log(postRoute()) // {} at /posts
console.log(postRoute()) // { postId: 'abc' } at /posts/abc

Nested Routes

import { reatomRoute } from '@reatom/framework'

const apiRoute = reatomRoute('api')
const productsRoute = apiRoute.reatomRoute('products')
const productRoute = productsRoute.reatomRoute(':productId')
const settingsRoute = productRoute.reatomRoute('settings')

// Navigate to nested route
settingsRoute.go({ productId: 'abc' })
// URL: /api/products/abc/settings

// Check matches at each level
console.log(apiRoute()) // {}
console.log(productsRoute()) // {}
console.log(productRoute()) // { productId: 'abc' }
console.log(settingsRoute()) // { productId: 'abc' }

// Check exact matches
console.log(apiRoute.exact()) // false
console.log(settingsRoute.exact()) // true

Parameter Validation

import { reatomRoute } from '@reatom/framework'
import { z } from 'zod'

const issueRoute = reatomRoute({
  path: 'issues/:issueId',
  params: z.object({
    issueId: z.string().regex(/^\d+$/).transform(Number),
  }),
})

// Type-safe navigation
issueRoute.go({ issueId: '123' })

// Params are validated and transformed
console.log(issueRoute()) // { issueId: 123 } (number, not string)

// Invalid params are rejected
issueRoute.go({ issueId: 'abc' }) // Throws error

Search Parameters

import { reatomRoute } from '@reatom/framework'
import { z } from 'zod'

const searchRoute = reatomRoute({
  path: 'search',
  search: z.object({
    q: z.string().optional(),
    page: z.string().transform(Number).default('1'),
    sort: z.enum(['asc', 'desc']).optional(),
  }),
})

searchRoute.go({ q: 'reatom', page: 2, sort: 'desc' })
// URL: /search?q=reatom&page=2&sort=desc

console.log(searchRoute())
// { q: 'reatom', page: 2, sort: 'desc' }

Data Loading

import { reatomRoute } from '@reatom/framework'
import { z } from 'zod'

const userRoute = reatomRoute({
  path: 'users/:userId',
  params: z.object({
    userId: z.string().regex(/^\d+$/).transform(Number),
  }),
  async loader({ userId }) {
    const response = await fetch(`/api/users/${userId}`)
    return response.json()
  },
})

userRoute.go({ userId: '123' })

// Access loader state
const user = await wrap(userRoute.loader())
console.log(userRoute.loader.data()) // User data
console.log(userRoute.loader.ready()) // true when loaded
console.log(userRoute.loader.error()) // Error if failed

// Loader automatically aborts when navigating away
userRoute.go({ userId: '456' }) // Previous load is aborted

Nested Loaders

import { reatomRoute } from '@reatom/framework'

const organizationRoute = reatomRoute({
  path: 'orgs/:orgId',
  async loader({ orgId }) {
    const org = await fetch(`/api/orgs/${orgId}`).then(r => r.json())
    return org
  },
})

const projectRoute = organizationRoute.reatomRoute({
  path: 'projects/:projectId',
  async loader({ orgId, projectId }) {
    // Parent loader waits first
    const project = await fetch(
      `/api/orgs/${orgId}/projects/${projectId}`
    ).then(r => r.json())
    return project
  },
})

// Parent loader executes before child
projectRoute.go({ orgId: '1', projectId: '2' })
// 1. organizationRoute.loader executes
// 2. projectRoute.loader executes after parent completes

Search-Only Routes

import { reatomRoute } from '@reatom/framework'
import { z } from 'zod'

// Route with no path - only manages search params
const dialogRoute = reatomRoute({
  search: z.object({
    dialog: z.enum(['login', 'signup']).optional(),
  }),
})

// Preserves current pathname, only updates search
urlAtom.go('/profile/123')
dialogRoute.go({ dialog: 'login' })
// URL: /profile/123?dialog=login

urlAtom.go('/settings')
dialogRoute.go({ dialog: 'signup' })
// URL: /settings?dialog=signup

Component Rendering

import { reatomRoute } from '@reatom/framework'
import { html } from 'lit'

const layoutRoute = reatomRoute({
  render(self) {
    // self() returns params when matched
    // self.outlet() returns child route components
    return html`
      <div>
        <header>My App</header>
        <main>${self.outlet().map((child) => child)}</main>
      </div>
    `
  },
})

const homeRoute = layoutRoute.reatomRoute({
  path: 'home',
  render() {
    return html`<h1>Home Page</h1>`
  },
})

const aboutRoute = layoutRoute.reatomRoute({
  path: 'about',
  render() {
    return html`<h1>About Page</h1>`
  },
})

// Access rendered components
console.log(layoutRoute.render()) // Layout component
console.log(layoutRoute.outlet()) // [homeRoute or aboutRoute component]

Exact Rendering

import { reatomRoute } from '@reatom/framework'

const projectRoute = reatomRoute({
  path: 'project',
  exactRender: true,
  render() {
    return html`<h1>Project Overview</h1>`
  },
})

const settingsRoute = projectRoute.reatomRoute({
  path: 'settings',
  render() {
    return html`<h1>Project Settings</h1>`
  },
})

// At /project
console.log(projectRoute.render()) // Project overview component

// At /project/settings
console.log(projectRoute.render()) // null (not exact match)
console.log(settingsRoute.render()) // Settings component

Callback-Based Params

import { reatomRoute } from '@reatom/framework'

// Custom validation logic
const dialogRoute = reatomRoute({
  params: ({ open }: { open: boolean }) => ({ open }),
})

const profileRoute = dialogRoute.reatomRoute({
  params: ({ profileOf }: { profileOf: string }) =>
    profileOf ? { profileOf } : null,
})

profileRoute.go({ open: true, profileOf: 'user123' })

console.log(dialogRoute()) // { open: true }
console.log(profileRoute()) // { profileOf: 'user123' }

profileRoute.go({ open: true, profileOf: '' })
console.log(profileRoute()) // null (validation failed)

404 Detection

import { reatomRoute, is404 } from '@reatom/framework'

const homeRoute = reatomRoute('')
const aboutRoute = reatomRoute('about')

// Navigate to unknown path
urlAtom.go('/unknown')

console.log(is404()) // true
console.log(homeRoute()) // null
console.log(aboutRoute()) // null

// Navigate to known path
homeRoute.go()
console.log(is404()) // false

Loader Pending State

import { reatomRoute, isSomeLoaderPending } from '@reatom/framework'

const route1 = reatomRoute({
  path: 'route1',
  async loader() {
    await sleep(1000)
    return {}
  },
})

const route2 = reatomRoute({
  path: 'route2',
  async loader() {
    await sleep(500)
    return {}
  },
})

route1.go()
console.log(isSomeLoaderPending()) // true

await wrap(route1.loader())
console.log(isSomeLoaderPending()) // false

Type Utilities

PathParams

Extract parameter types from a path pattern:
import { type PathParams } from '@reatom/framework'

type Params1 = PathParams<'users/:userId/posts/:postId?'>
// { userId: string; postId?: string }

type Params2 = PathParams<':id'>
// { id: string }