Getting the URL template in React Router and Remix

  • react-router
  • react-router v6
  • remix
  • url
  • template
  • sentry
Josh DeGraw

My team and I recently finally updated our routing to use React Router. We use sentry, and I wanted to take advantage of the routing setup to configure our sentry issues to be grouped better (previously they were grouped by the full url, which meant lots of new issues because it wouldn't disambiguate based on url parameters).

So I needed to get the full URL template string, which proved more difficult at first than I expected; I figured there would be a built-in way to get this, but I was wrong. I looked around and the only things I could find that came close were for older versions of React Router, but none of them would work in v6.

So I wrote a small hook myself that does exactly what I needed:

function useRouteTemplate() {
  const location = useLocation()
  const params = useParams()
  let finalString = location.pathname

  for (const [key, value] of Object.entries(params)) {
    if (value) {
      finalString = finalString.replace(value, `:${key}`)
    }
  }
  return finalString
}

So with React Router, if you had defined your routes like this:

import { BrowserRouter, Routes, Route } from 'react-router-dom'

export function RouteConfig() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Routes path="/posts">
          <Route path=":id" element={<Post />}>
            <Routes path="comments" element={<CommentList/>}>
              <Route path=":commentId" element={<Comment />} />
            </Routes>
          </Route>
        </Route>
      </Routes>
    </BrowserRouter>
  )
}

So if the current URL is /posts/123/comments/456, calling the useRouteTemplate() hook here will return /posts/:id/comments/:commentId, which is exactly what needed here.

In my case, I put it at a root element that always matches the URL, so I could use the hook like this and get the full URL template on every route so our sentry transaction should always be grouped by the full URL template:

import * as Sentry from '@sentry/react'
function Home() {
  const routeTemplate = useRouteTemplate()

  useEffect(() => {
    Sentry.configureScope(scope => scope.setTransactionName(routeTemplate))
  }, [routeTemplate])

  return (
    <>
      {/*Rest of the code*/}
      <Outlet />
    </>
  )
}