Skip to content

Commit fec16a7

Browse files
fix(router): replace legacy redirects with NotFoundPage to prevent redirect loop
The `:specId/:library` catch-all route was matching `/python/scatter-basic` (specId=python, library=scatter-basic) instead of the `python` route group, causing an infinite `/python/python/python/...` redirect loop. Replace all legacy redirect routes with a simple `*` catch-all that renders NotFoundPage. The site is young enough that old URLs don't need preservation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 32a933d commit fec16a7

File tree

1 file changed

+3
-28
lines changed

1 file changed

+3
-28
lines changed

app/src/router.tsx

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,18 @@
1-
import { createBrowserRouter, RouterProvider, Navigate, useParams } from 'react-router-dom';
1+
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
22
import { HelmetProvider } from 'react-helmet-async';
33
import Box from '@mui/material/Box';
44
import CircularProgress from '@mui/material/CircularProgress';
55
import { Layout, AppDataProvider } from './components/Layout';
66
import { ErrorBoundary } from './components/ErrorBoundary';
77
import { HomePage } from './pages/HomePage';
88
import { NotFoundPage } from './pages/NotFoundPage';
9-
import { specPath, interactivePath } from './utils/paths';
109

1110
const LazyFallback = () => (
1211
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '50vh' }}>
1312
<CircularProgress size={32} />
1413
</Box>
1514
);
1615

17-
// Catches old URLs without /python/ prefix and redirects to /python/ equivalents.
18-
// Uses splat (*) to avoid route ranking conflicts with the python route group.
19-
function LegacyCatchAll() {
20-
const params = useParams();
21-
const splatPath = params['*'] || '';
22-
const parts = splatPath.split('/').filter(Boolean);
23-
24-
if (parts.length === 1) {
25-
return <Navigate to={specPath(parts[0])} replace />;
26-
}
27-
if (parts.length === 2) {
28-
return <Navigate to={specPath(parts[0], parts[1])} replace />;
29-
}
30-
return <NotFoundPage />;
31-
}
32-
33-
function LegacyInteractiveRedirect() {
34-
const { specId, library } = useParams();
35-
return <Navigate to={interactivePath(specId!, library!)} replace />;
36-
}
37-
3816
const lazySpec = () => import('./pages/SpecPage').then(m => ({ Component: m.SpecPage, HydrateFallback: LazyFallback }));
3917

4018
const router = createBrowserRouter([
@@ -54,15 +32,12 @@ const router = createBrowserRouter([
5432
{ path: ':specId', lazy: lazySpec },
5533
{ path: ':specId/:library', lazy: lazySpec },
5634
]},
57-
// Legacy catch-all: redirects old /:specId and /:specId/:library to /python/ equivalents.
58-
// Uses * (lowest priority) so the python route group always wins.
59-
{ path: '*', element: <LegacyCatchAll /> },
35+
// Old URLs without /python/ prefix → redirect to homepage
36+
{ path: '*', element: <NotFoundPage /> },
6037
],
6138
},
6239
// Fullscreen interactive view (outside Layout)
6340
{ path: 'python/interactive/:specId/:library', lazy: () => import('./pages/InteractivePage').then(m => ({ Component: m.InteractivePage, HydrateFallback: LazyFallback })) },
64-
// Legacy interactive redirect
65-
{ path: 'interactive/:specId/:library', element: <LegacyInteractiveRedirect /> },
6641
// Hidden debug dashboard (outside Layout - no header/footer)
6742
{ path: 'debug', lazy: () => import('./pages/DebugPage').then(m => ({ Component: m.DebugPage, HydrateFallback: LazyFallback })) },
6843
{ path: '*', element: <NotFoundPage /> },

0 commit comments

Comments
 (0)