Fix crashes on unknown menu item / order routes

Both loaders threw a plain Error that the router doesn't treat as a
route-level not-found, so visiting /items/<bad-id> or /orders/<bad-id>
(or a valid order with a missing/wrong ?token) crashed instead of
showing the NotFound page. Menu item lookups now throw notFound()
directly; the order loader catches the server function's error and
maps both "order not found" and "wrong access token" to notFound(),
so a guessed id can't be used to probe which orders exist.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-10 21:21:29 +02:00
co-authored by Claude Sonnet 5
parent 927f6ff56e
commit 18a7d2030e
2 changed files with 28 additions and 10 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
import { Link, createFileRoute } from '@tanstack/react-router' import { Link, createFileRoute, notFound } from '@tanstack/react-router'
import { MenuItemDetail } from '#/components/ordering/MenuItemDetail.tsx' import { MenuItemDetail } from '#/components/ordering/MenuItemDetail.tsx'
import { useBasket } from '#/lib/basket.tsx' import { useBasket } from '#/lib/basket.tsx'
@@ -48,7 +48,7 @@ export const Route = createFileRoute('/items/$menuItemId')({
const brand = await getBrand() const brand = await getBrand()
const menu = await getMenu({ data: { brandId: brand.id } }) const menu = await getMenu({ data: { brandId: brand.id } })
const item = menu.find((menuItem) => menuItem.id === Number(params.menuItemId)) const item = menu.find((menuItem) => menuItem.id === Number(params.menuItemId))
if (!item) throw new Error('Menu item not found') if (!item) throw notFound()
return { item } return { item }
}, },
component: MenuItemRoute, component: MenuItemRoute,
+22 -4
View File
@@ -1,4 +1,4 @@
import { createFileRoute } from '@tanstack/react-router' import { createFileRoute, notFound } from '@tanstack/react-router'
import { z } from 'zod' import { z } from 'zod'
import { OrderStatusView } from '#/components/orders/OrderStatusView.tsx' import { OrderStatusView } from '#/components/orders/OrderStatusView.tsx'
@@ -6,6 +6,16 @@ import { getOrderStatus } from '#/server/orders.ts'
const searchSchema = z.object({ token: z.string().optional() }) const searchSchema = z.object({ token: z.string().optional() })
// Errors cross the server-function boundary as a plain `Error` carrying only
// `.message` (TanStack Start's ShallowErrorPlugin drops the subclass), so an
// unknown order and a wrong/missing guest token are told apart by message
// rather than `instanceof`. Both collapse to a 404 rather than a real error
// page, so a guessed order id can't be used to probe which ones exist.
const notFoundMessages = new Set([
'Order not found',
'You do not have access to this Order',
])
const OrderStatusRoute = () => { const OrderStatusRoute = () => {
const status = Route.useLoaderData() const status = Route.useLoaderData()
const { token } = Route.useSearch() const { token } = Route.useSearch()
@@ -17,12 +27,20 @@ export const Route = createFileRoute('/orders/$orderId')({
validateSearch: (search: Record<string, unknown>) => validateSearch: (search: Record<string, unknown>) =>
searchSchema.parse(search), searchSchema.parse(search),
loaderDeps: ({ search }) => ({ token: search.token }), loaderDeps: ({ search }) => ({ token: search.token }),
loader: ({ params, deps }) => loader: async ({ params, deps }) => {
getOrderStatus({ try {
return await getOrderStatus({
data: { data: {
orderId: Number(params.orderId), orderId: Number(params.orderId),
guestAccessToken: deps.token, guestAccessToken: deps.token,
}, },
}), })
} catch (error) {
if (error instanceof Error && notFoundMessages.has(error.message)) {
throw notFound()
}
throw error
}
},
component: OrderStatusRoute, component: OrderStatusRoute,
}) })