Skip to main content

Pattern Syntax

Route patterns use a simple syntax for matching URLs and extracting parameters:
  • Static segments: /blog/posts
  • Dynamic segments: /blog/:slug - Matches any value
  • Optional segments: /blog/:slug? - Matches with or without the segment
  • Wildcard segments: /files/* - Matches remaining path
import { route } from 'remix/fetch-router/routes'

let routes = route({
  // Static route
  home: '/',
  
  // Dynamic parameter
  post: '/blog/:slug',
  
  // Multiple parameters
  comment: '/blog/:postSlug/comments/:commentId',
  
  // Optional parameter
  search: '/search/:query?',
  
  // Wildcard (matches everything after /files/)
  files: '/files/*',
})

Type-Safe Parameters

Route parameters are automatically inferred from your patterns and fully type-checked:
router.map(routes, {
  actions: {
    post({ params }) {
      // params.slug is a string
      console.log(params.slug)
      return new Response(`Post: ${params.slug}`)
    },
    comment({ params }) {
      // Both parameters are typed
      console.log(params.postSlug, params.commentId)
      return new Response('Comment')
    },
    search({ params }) {
      // Optional parameter is string | undefined
      if (params.query) {
        console.log(`Searching for: ${params.query}`)
      }
      return new Response('Search')
    },
    files({ params }) {
      // Wildcard matches are available as params['*']
      console.log(params['*']) // e.g., "documents/report.pdf"
      return new Response('Files')
    },
  },
})

Declaring Routes

There are several ways to declare routes:

Simple String Patterns

The simplest way is to use string patterns directly:
let routes = route({
  home: '/',
  about: '/about',
  contact: '/contact',
})

type HomeRoute = typeof routes.home
// Route<'ANY', '/'>
These routes match any HTTP method (GET, POST, etc.).

Method-Specific Routes

Specify the HTTP method in the route definition:
let routes = route({
  getPosts: { method: 'GET', pattern: '/posts' },
  createPost: { method: 'POST', pattern: '/posts' },
  updatePost: { method: 'PUT', pattern: '/posts/:id' },
  deletePost: { method: 'DELETE', pattern: '/posts/:id' },
})

type GetPostsRoute = typeof routes.getPosts
// Route<'GET', '/posts'>
method
RequestMethod
required
The HTTP method to match: 'GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'
pattern
string | RoutePattern
required
The URL pattern to match

Method Helper Functions

Use helper functions for common HTTP methods:
import { get, post, put, del } from 'remix/fetch-router/routes'

let routes = route({
  getPosts: get('/posts'),
  createPost: post('/posts'),
  updatePost: put('/posts/:id'),
  deletePost: del('/posts/:id'),
})
Available helpers:
  • get(pattern) - GET request
  • post(pattern) - POST request
  • put(pattern) - PUT request
  • patch(pattern) - PATCH request
  • del(pattern) - DELETE request (aliased from createDeleteRoute)
  • head(pattern) - HEAD request
  • options(pattern) - OPTIONS request

Generating URLs

Every route has an href() method for generating type-safe URLs:
let routes = route({
  home: '/',
  post: '/blog/:slug',
  comment: '/blog/:postSlug/comments/:commentId',
})

// Simple route (no parameters)
routes.home.href() // "/"

// Route with one parameter
routes.post.href({ slug: 'hello-world' }) // "/blog/hello-world"

// Route with multiple parameters
routes.comment.href({
  postSlug: 'hello-world',
  commentId: '123',
}) // "/blog/hello-world/comments/123"

// Add query parameters
routes.post.href(
  { slug: 'hello-world' },
  { searchParams: { sort: 'date', order: 'desc' } },
) // "/blog/hello-world?sort=date&order=desc"
params
object
Parameters to fill into the route pattern. Required if the pattern has dynamic segments.
options
object
Additional options for URL generation

Using href() in HTML

Use href() to generate type-safe links and form actions:
import { html } from 'remix/html-template'
import { createHtmlResponse } from 'remix/response/html'

router.get(routes.home, () => {
  return createHtmlResponse(html`
    <html>
      <body>
        <nav>
          <a href="${routes.home.href()}">Home</a>
          <a href="${routes.post.href({ slug: 'hello-world' })}">Hello World</a>
        </nav>
        <form method="POST" action="${routes.createPost.href()}">
          <input type="text" name="title" />
          <button type="submit">Create</button>
        </form>
      </body>
    </html>
  `)
})

Pattern Matching

Routes can test whether a URL matches their pattern:
let route = new Route('GET', '/blog/:slug')

// Match a URL
let match = route.match('https://example.com/blog/hello-world')
if (match) {
  console.log(match.params.slug) // "hello-world"
}

// No match
let noMatch = route.match('https://example.com/about')
console.log(noMatch) // null

Nested Routes

Organize routes hierarchically with nested objects:
let routes = route({
  home: '/',
  blog: {
    index: '/blog',
    show: '/blog/:slug',
    comments: {
      index: '/blog/:slug/comments',
      show: '/blog/:slug/comments/:commentId',
    },
  },
  admin: {
    dashboard: '/admin',
    users: {
      index: '/admin/users',
      show: '/admin/users/:id',
      edit: '/admin/users/:id/edit',
    },
  },
})

// Access nested routes
routes.blog.show.href({ slug: 'hello' })
routes.blog.comments.show.href({
  slug: 'hello',
  commentId: '123',
})
routes.admin.users.edit.href({ id: '456' })

Base Patterns

Define a base pattern for all routes in a group:
import { createRoutes } from 'remix/fetch-router/routes'

// All routes will be prefixed with /api/v1
let apiRoutes = createRoutes('/api/v1', {
  users: '/users',
  posts: '/posts',
  comments: '/comments',
})

apiRoutes.users.href() // "/api/v1/users"
apiRoutes.posts.href() // "/api/v1/posts"
base
string | RoutePattern
required
The base pattern to prepend to all routes
defs
RouteDefs
required
The route definitions

RoutePattern Class

For advanced use cases, you can work with RoutePattern directly:
import { RoutePattern } from '@remix-run/route-pattern'

let pattern = new RoutePattern('/blog/:slug')

// Generate URLs
pattern.href({ slug: 'hello' }) // "/blog/hello"

// Match URLs
let match = pattern.match('https://example.com/blog/hello')
console.log(match?.params.slug) // "hello"

// Join patterns
let base = new RoutePattern('/api')
let joined = base.join('/users/:id')
joined.href({ id: '123' }) // "/api/users/123"

Parameter Constraints

While route patterns don’t support regex constraints, you can validate parameters in your action:
router.get('/users/:id', ({ params }) => {
  // Validate that id is a number
  if (!/^\d+$/.test(params.id)) {
    return new Response('Invalid user ID', { status: 400 })
  }
  
  return Response.json({ userId: params.id })
})

Wildcard Matching

Wildcard segments match the rest of the path:
let routes = route({
  files: '/files/*',
})

router.get(routes.files, ({ params }) => {
  // Access wildcard match with params['*']
  let filePath = params['*'] // e.g., "documents/report.pdf"
  return new Response(`File: ${filePath}`)
})
The wildcard segment must be the last segment in the pattern.

Next Steps

Middleware

Add reusable functionality to routes

Controllers

Organize route actions with controllers

Forms

Learn about form routes and RESTful resources

Build docs developers (and LLMs) love