studio/frontend: friendlier 404 fallback for unknown routes (#5664)

* studio/frontend: friendlier 404 fallback for unknown routes

TanStack Router defaults to a bare "Not Found" string when no
route matches. With Studio's root layout that string sits alone
in the main content area while the sidebar still renders, which
looks broken when the user hits a typo'd path, a stale share
link, or a chat URL with an extra path segment.

Provide a small DefaultNotFound component to createRouter:
sloth mascot, "Page not found" heading, the offending pathname,
and a Back to chat button. Studio chrome continues to render
around it, so the user gets the same sidebar nav for free.

Resolves #5663.

* studio/frontend: 404 fallback uses useRouterState + URL-encoded sloth path

Address review feedback on #5664:
- Read pathname via useRouterState({ select: s => s.location.pathname })
  instead of window.location.pathname. Matches the pattern already used
  in __root.tsx, drops the window-typeof guard, and stays consistent with
  the router store on subsequent client navigations.
- URL-encode the sloth mascot src so the space-containing path resolves
  cleanly without relying on the browser to encode it.
- Add break-all on the pathname paragraph so long offending URLs wrap
  instead of pushing the card wider than the viewport.

---------

Co-authored-by: danielhanchen <michaelhan2050@gmail.com>
This commit is contained in:
Daniel Han 2026-05-22 04:30:32 -07:00 committed by GitHub
commit 43beeae39d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,7 +1,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
import { createRouter } from "@tanstack/react-router";
import { Link, createRouter, useRouterState } from "@tanstack/react-router";
import { Button } from "@/components/ui/button";
import { Route as rootRoute } from "./routes/__root";
import { Route as dataRecipesRoute } from "./routes/data-recipes";
import { Route as dataRecipeRoute } from "./routes/data-recipes.$recipeId";
@ -29,7 +30,34 @@ const routeTree = rootRoute.addChildren([
dataRecipeRoute,
]);
export const router = createRouter({ routeTree });
function DefaultNotFound() {
const pathname = useRouterState({ select: (s) => s.location.pathname });
return (
<div className="flex flex-1 flex-col items-center justify-center gap-4 p-8 text-center">
<img
src="/Sloth%20emojis/sloth%20shy%20large.png"
alt="Sloth mascot"
className="size-24"
/>
<div className="flex flex-col items-center gap-1">
<h1 className="font-heading font-semibold text-2xl tracking-tight">
Page not found
</h1>
<p className="text-muted-foreground text-sm break-all">
{pathname} does not exist.
</p>
</div>
<Button asChild>
<Link to="/chat">Back to chat</Link>
</Button>
</div>
);
}
export const router = createRouter({
routeTree,
defaultNotFoundComponent: DefaultNotFound,
});
declare module "@tanstack/react-router" {
interface Register {