When you name a route segment with $ like
routes/users/$userId.js, the $ segment will be parsed from the URL and sent to your loaders and actions by the same name.
When a route throws and error in it's action, loader, or component, Remix automatically catches it, won't even try to render the component, but it will render the route's ErrorBoundary instead. If the route doesn't have one, it will bubble up to the routes above it until it hits the root.
So be as granular as you want with your error handling.
(and other client errors)
Loaders and Actions can throw a
Response instead of an error and Remix will render the CatchBoundary instead of the component. This is great when loading data from a database isn't found. As soon as you know you can't render the component normally, throw a 404 response and send your app into the catch boundary. Just like error boundaries, catch boundaries bubble, too.