feat(studio): land /org/_/* and /project/_/* catch-alls

Adds the four TanStack routes for the wildcard org/project pages:
  routes/org.[_].tsx       - matches /org/_ exactly
  routes/org.[_].$.tsx     - matches /org/_/...
  routes/project.[_].tsx   - matches /project/_ exactly
  routes/project.[_].$.tsx - matches /project/_/...

Files use the path-as-filename form (org.[_].tsx) rather than the
nested-directory + index.tsx form because the directory form trips a
router-generator bug at getRouteNodes.js:132: when an index.tsx has a
bracket-escaped parent segment, originalRoutePath is wiped wholesale
and the escape info is lost - downstream the literal _ segment gets
stripped as if it were a pathless layout. Full rationale lives in the
comment block in routes/org.[_].tsx.

Next pages now accept either Next-style routeSlug (string[]) or
TanStack-style _splat (string) and normalise to the array shape, so
both runtimes can mount the same body.
This commit is contained in:
Alaister Young committed 2026-05-11 14:24:46 +08:00
1 parent 27f6d9255f
commit f2cf9bf8d9
8 files changed
+203 -4

No files matched your search

+2 -2
View File
@@ -383,8 +383,8 @@ per-file items only when a subtree has special cases.
## Deferred / revisit
- `pages/org/_/[[...routeSlug]].tsx` — catch-all placeholder; revisit after all `/org/*` pages are migrated
- `pages/project/_/[[...routeSlug]].tsx` — catch-all placeholder; revisit after all `/project/*` pages are migrated
- ~~`pages/org/_/[[...routeSlug]].tsx`~~ landed as `routes/org.[_].tsx` + `routes/org.[_].$.tsx`. **Naming delta:** path-as-filename form (not `routes/org/[_]/index.tsx`) because the index-file form trips a router-generator bug at `getRouteNodes.js:132` — when an `index.tsx` has a bracket-escaped _parent_ segment, `originalRoutePath` gets wiped wholesale and the escape info is lost, so `_` gets stripped as pathless. The path-as-filename form keeps the last segment non-index and avoids the bug branch entirely. Next page accepts either Next-style `routeSlug` (string[]) or TanStack-style `_splat` (string) and normalises to the array shape.
- ~~`pages/project/_/[[...routeSlug]].tsx`~~ landed as `routes/project.[_].tsx` + `routes/project.[_].$.tsx`. Same naming-delta rationale as the org catch-alls above.
- Remove `RouteValidationWrapper` + `next/router` compat shim once no page under `pages/` remains
- Remove `compat/next/` directory entirely once all Next imports are gone from workspace source
- Delete this file
+10 -1
View File
@@ -20,7 +20,16 @@ import { buildStudioPageTitle } from '@/lib/page-title'
const GenericOrganizationPage: NextPage = () => {
const router = useRouter()
const { appTitle } = useCustomContent(['app:title'])
const { routeSlug, ...queryParams } = router.query
// Under TanStack this page is mounted via `routes/org/[_]/$.tsx`,
// which surfaces the trailing path as `_splat` (string), not as Next's
// `routeSlug` (string[]). Normalise both shapes to an array so
// downstream URL building (buildOrgUrl) keeps working.
const { routeSlug: rawRouteSlug, _splat, ...queryParams } = router.query
const routeSlug: string[] | undefined = Array.isArray(rawRouteSlug)
? rawRouteSlug
: typeof _splat === 'string' && _splat
? _splat.split('/')
: undefined
const queryString =
Object.keys(queryParams).length > 0
? new URLSearchParams(queryParams as Record<string, string>).toString()
@@ -35,7 +35,16 @@ import { withAuth } from '@/hooks/misc/withAuth'
const GenericProjectPage: NextPage = () => {
const router = useRouter()
const { slug } = useParams()
const { routeSlug, ...queryParams } = router.query
// Under TanStack this page is mounted via `routes/project/[_]/$.tsx`,
// which surfaces the trailing path as `_splat` (string), not as Next's
// `routeSlug` (string[]). Normalise both shapes to an array so
// downstream URL building (urlRewriterFactory) keeps working.
const { routeSlug: rawRouteSlug, _splat, ...queryParams } = router.query
const routeSlug: string[] | undefined = Array.isArray(rawRouteSlug)
? rawRouteSlug
: typeof _splat === 'string' && _splat
? _splat.split('/')
: undefined
const [lastVisitedOrgSlug] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.LAST_VISITED_ORGANIZATION,
+103
View File
@@ -19,7 +19,9 @@ import { Route as AuthorizeRouteImport } from './routes/authorize'
import { Route as AuthRouteImport } from './routes/_auth'
import { Route as AppRouteImport } from './routes/_app'
import { Route as IndexRouteImport } from './routes/index'
import { Route as ProjectChar91_Char93RouteImport } from './routes/project.[_]'
import { Route as ProjectRefRouteImport } from './routes/project/$ref'
import { Route as OrgChar91_Char93RouteImport } from './routes/org.[_]'
import { Route as NewSlugRouteImport } from './routes/new/$slug'
import { Route as IntegrationsVercelRouteImport } from './routes/integrations/vercel'
import { Route as ApiStatusOverrideRouteImport } from './routes/api/status-override'
@@ -47,6 +49,7 @@ import { Route as ProjectRefIndexRouteImport } from './routes/project/$ref/index
import { Route as ApiConnectIndexRouteImport } from './routes/api/connect/index'
import { Route as AppOrgIndexRouteImport } from './routes/_app/org/index'
import { Route as AppNewIndexRouteImport } from './routes/_app/new/index'
import { Route as ProjectChar91_Char93SplatRouteImport } from './routes/project.[_].$'
import { Route as ProjectRefStorageRouteImport } from './routes/project/$ref/storage'
import { Route as ProjectRefSqlRouteImport } from './routes/project/$ref/sql'
import { Route as ProjectRefSettingsRouteImport } from './routes/project/$ref/settings'
@@ -61,6 +64,7 @@ import { Route as ProjectRefDatabaseRouteImport } from './routes/project/$ref/da
import { Route as ProjectRefBranchesRouteImport } from './routes/project/$ref/branches'
import { Route as ProjectRefAuthRouteImport } from './routes/project/$ref/auth'
import { Route as ProjectRefAdvisorsRouteImport } from './routes/project/$ref/advisors'
import { Route as OrgChar91_Char93SplatRouteImport } from './routes/org.[_].$'
import { Route as IntegrationsVercelInstallRouteImport } from './routes/integrations/vercel/install'
import { Route as IntegrationsGithubAuthorizeRouteImport } from './routes/integrations/github/authorize'
import { Route as ApiIntegrationsStripeSyncRouteImport } from './routes/api/integrations/stripe-sync'
@@ -344,11 +348,21 @@ const IndexRoute = IndexRouteImport.update({
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
const ProjectChar91_Char93Route = ProjectChar91_Char93RouteImport.update({
id: '/project/_',
path: '/project/_',
getParentRoute: () => rootRouteImport,
} as any)
const ProjectRefRoute = ProjectRefRouteImport.update({
id: '/project/$ref',
path: '/project/$ref',
getParentRoute: () => rootRouteImport,
} as any)
const OrgChar91_Char93Route = OrgChar91_Char93RouteImport.update({
id: '/org/_',
path: '/org/_',
getParentRoute: () => rootRouteImport,
} as any)
const NewSlugRoute = NewSlugRouteImport.update({
id: '/new/$slug',
path: '/new/$slug',
@@ -486,6 +500,12 @@ const AppNewIndexRoute = AppNewIndexRouteImport.update({
path: '/new/',
getParentRoute: () => AppRoute,
} as any)
const ProjectChar91_Char93SplatRoute =
ProjectChar91_Char93SplatRouteImport.update({
id: '/$',
path: '/$',
getParentRoute: () => ProjectChar91_Char93Route,
} as any)
const ProjectRefStorageRoute = ProjectRefStorageRouteImport.update({
id: '/storage',
path: '/storage',
@@ -556,6 +576,11 @@ const ProjectRefAdvisorsRoute = ProjectRefAdvisorsRouteImport.update({
path: '/advisors',
getParentRoute: () => ProjectRefRoute,
} as any)
const OrgChar91_Char93SplatRoute = OrgChar91_Char93SplatRouteImport.update({
id: '/$',
path: '/$',
getParentRoute: () => OrgChar91_Char93Route,
} as any)
const IntegrationsVercelInstallRoute =
IntegrationsVercelInstallRouteImport.update({
id: '/install',
@@ -1921,7 +1946,9 @@ export interface FileRoutesByFullPath {
'/api/status-override': typeof ApiStatusOverrideRoute
'/integrations/vercel': typeof IntegrationsVercelRouteWithChildren
'/new/$slug': typeof NewSlugRoute
'/org/_': typeof OrgChar91_Char93RouteWithChildren
'/project/$ref': typeof ProjectRefRouteWithChildren
'/project/_': typeof ProjectChar91_Char93RouteWithChildren
'/account/audit': typeof AppAccountAuditRoute
'/account/me': typeof AppAccountMeRoute
'/account/security': typeof AppAccountSecurityRoute
@@ -1934,6 +1961,7 @@ export interface FileRoutesByFullPath {
'/api/integrations/stripe-sync': typeof ApiIntegrationsStripeSyncRoute
'/integrations/github/authorize': typeof IntegrationsGithubAuthorizeRoute
'/integrations/vercel/install': typeof IntegrationsVercelInstallRoute
'/org/_/$': typeof OrgChar91_Char93SplatRoute
'/project/$ref/advisors': typeof ProjectRefAdvisorsRouteWithChildren
'/project/$ref/auth': typeof ProjectRefAuthRouteWithChildren
'/project/$ref/branches': typeof ProjectRefBranchesRouteWithChildren
@@ -1948,6 +1976,7 @@ export interface FileRoutesByFullPath {
'/project/$ref/settings': typeof ProjectRefSettingsRouteWithChildren
'/project/$ref/sql': typeof ProjectRefSqlRouteWithChildren
'/project/$ref/storage': typeof ProjectRefStorageRouteWithChildren
'/project/_/$': typeof ProjectChar91_Char93SplatRoute
'/new/': typeof AppNewIndexRoute
'/org/': typeof AppOrgIndexRoute
'/api/connect/': typeof ApiConnectIndexRoute
@@ -2205,6 +2234,8 @@ export interface FileRoutesByTo {
'/api/status-override': typeof ApiStatusOverrideRoute
'/integrations/vercel': typeof IntegrationsVercelRouteWithChildren
'/new/$slug': typeof NewSlugRoute
'/org/_': typeof OrgChar91_Char93RouteWithChildren
'/project/_': typeof ProjectChar91_Char93RouteWithChildren
'/account/audit': typeof AppAccountAuditRoute
'/account/me': typeof AppAccountMeRoute
'/account/security': typeof AppAccountSecurityRoute
@@ -2217,6 +2248,7 @@ export interface FileRoutesByTo {
'/api/integrations/stripe-sync': typeof ApiIntegrationsStripeSyncRoute
'/integrations/github/authorize': typeof IntegrationsGithubAuthorizeRoute
'/integrations/vercel/install': typeof IntegrationsVercelInstallRoute
'/org/_/$': typeof OrgChar91_Char93SplatRoute
'/project/$ref/advisors': typeof ProjectRefAdvisorsRouteWithChildren
'/project/$ref/auth': typeof ProjectRefAuthRouteWithChildren
'/project/$ref/database': typeof ProjectRefDatabaseRouteWithChildren
@@ -2224,6 +2256,7 @@ export interface FileRoutesByTo {
'/project/$ref/realtime': typeof ProjectRefRealtimeRouteWithChildren
'/project/$ref/settings': typeof ProjectRefSettingsRouteWithChildren
'/project/$ref/storage': typeof ProjectRefStorageRouteWithChildren
'/project/_/$': typeof ProjectChar91_Char93SplatRoute
'/new': typeof AppNewIndexRoute
'/org': typeof AppOrgIndexRoute
'/api/connect': typeof ApiConnectIndexRoute
@@ -2482,7 +2515,9 @@ export interface FileRoutesById {
'/api/status-override': typeof ApiStatusOverrideRoute
'/integrations/vercel': typeof IntegrationsVercelRouteWithChildren
'/new/$slug': typeof NewSlugRoute
'/org/_': typeof OrgChar91_Char93RouteWithChildren
'/project/$ref': typeof ProjectRefRouteWithChildren
'/project/_': typeof ProjectChar91_Char93RouteWithChildren
'/_app/account/audit': typeof AppAccountAuditRoute
'/_app/account/me': typeof AppAccountMeRoute
'/_app/account/security': typeof AppAccountSecurityRoute
@@ -2495,6 +2530,7 @@ export interface FileRoutesById {
'/api/integrations/stripe-sync': typeof ApiIntegrationsStripeSyncRoute
'/integrations/github/authorize': typeof IntegrationsGithubAuthorizeRoute
'/integrations/vercel/install': typeof IntegrationsVercelInstallRoute
'/org/_/$': typeof OrgChar91_Char93SplatRoute
'/project/$ref/advisors': typeof ProjectRefAdvisorsRouteWithChildren
'/project/$ref/auth': typeof ProjectRefAuthRouteWithChildren
'/project/$ref/branches': typeof ProjectRefBranchesRouteWithChildren
@@ -2509,6 +2545,7 @@ export interface FileRoutesById {
'/project/$ref/settings': typeof ProjectRefSettingsRouteWithChildren
'/project/$ref/sql': typeof ProjectRefSqlRouteWithChildren
'/project/$ref/storage': typeof ProjectRefStorageRouteWithChildren
'/project/_/$': typeof ProjectChar91_Char93SplatRoute
'/_app/new/': typeof AppNewIndexRoute
'/_app/org/': typeof AppOrgIndexRoute
'/api/connect/': typeof ApiConnectIndexRoute
@@ -2769,7 +2806,9 @@ export interface FileRouteTypes {
| '/api/status-override'
| '/integrations/vercel'
| '/new/$slug'
| '/org/_'
| '/project/$ref'
| '/project/_'
| '/account/audit'
| '/account/me'
| '/account/security'
@@ -2782,6 +2821,7 @@ export interface FileRouteTypes {
| '/api/integrations/stripe-sync'
| '/integrations/github/authorize'
| '/integrations/vercel/install'
| '/org/_/$'
| '/project/$ref/advisors'
| '/project/$ref/auth'
| '/project/$ref/branches'
@@ -2796,6 +2836,7 @@ export interface FileRouteTypes {
| '/project/$ref/settings'
| '/project/$ref/sql'
| '/project/$ref/storage'
| '/project/_/$'
| '/new/'
| '/org/'
| '/api/connect/'
@@ -3053,6 +3094,8 @@ export interface FileRouteTypes {
| '/api/status-override'
| '/integrations/vercel'
| '/new/$slug'
| '/org/_'
| '/project/_'
| '/account/audit'
| '/account/me'
| '/account/security'
@@ -3065,6 +3108,7 @@ export interface FileRouteTypes {
| '/api/integrations/stripe-sync'
| '/integrations/github/authorize'
| '/integrations/vercel/install'
| '/org/_/$'
| '/project/$ref/advisors'
| '/project/$ref/auth'
| '/project/$ref/database'
@@ -3072,6 +3116,7 @@ export interface FileRouteTypes {
| '/project/$ref/realtime'
| '/project/$ref/settings'
| '/project/$ref/storage'
| '/project/_/$'
| '/new'
| '/org'
| '/api/connect'
@@ -3329,7 +3374,9 @@ export interface FileRouteTypes {
| '/api/status-override'
| '/integrations/vercel'
| '/new/$slug'
| '/org/_'
| '/project/$ref'
| '/project/_'
| '/_app/account/audit'
| '/_app/account/me'
| '/_app/account/security'
@@ -3342,6 +3389,7 @@ export interface FileRouteTypes {
| '/api/integrations/stripe-sync'
| '/integrations/github/authorize'
| '/integrations/vercel/install'
| '/org/_/$'
| '/project/$ref/advisors'
| '/project/$ref/auth'
| '/project/$ref/branches'
@@ -3356,6 +3404,7 @@ export interface FileRouteTypes {
| '/project/$ref/settings'
| '/project/$ref/sql'
| '/project/$ref/storage'
| '/project/_/$'
| '/_app/new/'
| '/_app/org/'
| '/api/connect/'
@@ -3606,7 +3655,9 @@ export interface RootRouteChildren {
ApiStatusOverrideRoute: typeof ApiStatusOverrideRoute
IntegrationsVercelRoute: typeof IntegrationsVercelRouteWithChildren
NewSlugRoute: typeof NewSlugRoute
OrgChar91_Char93Route: typeof OrgChar91_Char93RouteWithChildren
ProjectRefRoute: typeof ProjectRefRouteWithChildren
ProjectChar91_Char93Route: typeof ProjectChar91_Char93RouteWithChildren
ApiAiDocsRoute: typeof ApiAiDocsRoute
ApiContentGraphqlRoute: typeof ApiContentGraphqlRoute
ApiEdgeFunctionsTestRoute: typeof ApiEdgeFunctionsTestRoute
@@ -3761,6 +3812,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
'/project/_': {
id: '/project/_'
path: '/project/_'
fullPath: '/project/_'
preLoaderRoute: typeof ProjectChar91_Char93RouteImport
parentRoute: typeof rootRouteImport
}
'/project/$ref': {
id: '/project/$ref'
path: '/project/$ref'
@@ -3768,6 +3826,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ProjectRefRouteImport
parentRoute: typeof rootRouteImport
}
'/org/_': {
id: '/org/_'
path: '/org/_'
fullPath: '/org/_'
preLoaderRoute: typeof OrgChar91_Char93RouteImport
parentRoute: typeof rootRouteImport
}
'/new/$slug': {
id: '/new/$slug'
path: '/new/$slug'
@@ -3957,6 +4022,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AppNewIndexRouteImport
parentRoute: typeof AppRoute
}
'/project/_/$': {
id: '/project/_/$'
path: '/$'
fullPath: '/project/_/$'
preLoaderRoute: typeof ProjectChar91_Char93SplatRouteImport
parentRoute: typeof ProjectChar91_Char93Route
}
'/project/$ref/storage': {
id: '/project/$ref/storage'
path: '/storage'
@@ -4055,6 +4127,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ProjectRefAdvisorsRouteImport
parentRoute: typeof ProjectRefRoute
}
'/org/_/$': {
id: '/org/_/$'
path: '/$'
fullPath: '/org/_/$'
preLoaderRoute: typeof OrgChar91_Char93SplatRouteImport
parentRoute: typeof OrgChar91_Char93Route
}
'/integrations/vercel/install': {
id: '/integrations/vercel/install'
path: '/install'
@@ -5813,6 +5892,17 @@ const IntegrationsVercelRouteChildren: IntegrationsVercelRouteChildren = {
const IntegrationsVercelRouteWithChildren =
IntegrationsVercelRoute._addFileChildren(IntegrationsVercelRouteChildren)
interface OrgChar91_Char93RouteChildren {
OrgChar91_Char93SplatRoute: typeof OrgChar91_Char93SplatRoute
}
const OrgChar91_Char93RouteChildren: OrgChar91_Char93RouteChildren = {
OrgChar91_Char93SplatRoute: OrgChar91_Char93SplatRoute,
}
const OrgChar91_Char93RouteWithChildren =
OrgChar91_Char93Route._addFileChildren(OrgChar91_Char93RouteChildren)
interface ProjectRefAdvisorsRulesRouteChildren {
ProjectRefAdvisorsRulesPerformanceRoute: typeof ProjectRefAdvisorsRulesPerformanceRoute
ProjectRefAdvisorsRulesSecurityRoute: typeof ProjectRefAdvisorsRulesSecurityRoute
@@ -6308,6 +6398,17 @@ const ProjectRefRouteWithChildren = ProjectRefRoute._addFileChildren(
ProjectRefRouteChildren,
)
interface ProjectChar91_Char93RouteChildren {
ProjectChar91_Char93SplatRoute: typeof ProjectChar91_Char93SplatRoute
}
const ProjectChar91_Char93RouteChildren: ProjectChar91_Char93RouteChildren = {
ProjectChar91_Char93SplatRoute: ProjectChar91_Char93SplatRoute,
}
const ProjectChar91_Char93RouteWithChildren =
ProjectChar91_Char93Route._addFileChildren(ProjectChar91_Char93RouteChildren)
interface ApiPlatformProjectsRefAnalyticsLogDrainsRouteChildren {
ApiPlatformProjectsRefAnalyticsLogDrainsUuidRoute: typeof ApiPlatformProjectsRefAnalyticsLogDrainsUuidRoute
}
@@ -6346,7 +6447,9 @@ const rootRouteChildren: RootRouteChildren = {
ApiStatusOverrideRoute: ApiStatusOverrideRoute,
IntegrationsVercelRoute: IntegrationsVercelRouteWithChildren,
NewSlugRoute: NewSlugRoute,
OrgChar91_Char93Route: OrgChar91_Char93RouteWithChildren,
ProjectRefRoute: ProjectRefRouteWithChildren,
ProjectChar91_Char93Route: ProjectChar91_Char93RouteWithChildren,
ApiAiDocsRoute: ApiAiDocsRoute,
ApiContentGraphqlRoute: ApiContentGraphqlRoute,
ApiEdgeFunctionsTestRoute: ApiEdgeFunctionsTestRoute,
+20
View File
@@ -0,0 +1,20 @@
import { createFileRoute } from '@tanstack/react-router'
import GenericOrganizationPage from '@/pages/org/_/[[...routeSlug]]'
export const Route = createFileRoute('/org/_/$')({
component: OrgWildcardSplatRoute,
})
// Matches `/org/_/...`. TanStack exposes the trailing path as the
// `_splat` param; the underlying Next page normalises it back to the
// `routeSlug: string[]` shape it originally consumed.
//
// Naming delta: `org.[_].$.tsx` (path-as-filename) rather than
// `org/[_]/$.tsx` (nested directory) — see `org.[_].tsx` for the full
// rationale; in short, the directory + index.tsx form trips a
// router-generator bug that strips the escaped `_` segment when the
// last segment isn't escaped.
function OrgWildcardSplatRoute() {
return <GenericOrganizationPage dehydratedState={undefined} />
}
+23
View File
@@ -0,0 +1,23 @@
import { createFileRoute } from '@tanstack/react-router'
import GenericOrganizationPage from '@/pages/org/_/[[...routeSlug]]'
export const Route = createFileRoute('/org/_')({
component: OrgWildcardIndexRoute,
})
// Matches `/org/_` exactly. The companion splat route at
// `org.[_].$.tsx` handles `/org/_/...`.
//
// `[_]` is TanStack's escape syntax for a literal underscore in the URL
// (otherwise an unescaped `_` segment is treated as a pathless layout).
//
// Naming delta: this is `org.[_].tsx`, not `org/[_]/index.tsx`, to side-
// step a router-generator bug — when an index.tsx's path has a bracket-
// escaped *parent* segment, `originalRoutePath` gets wiped wholesale and
// the escape info is lost (getRouteNodes.js:132 sets originalRoutePath
// to `/`). The path-as-filename form keeps the last segment non-index,
// so the bug branch is skipped.
function OrgWildcardIndexRoute() {
return <GenericOrganizationPage dehydratedState={undefined} />
}
+20
View File
@@ -0,0 +1,20 @@
import { createFileRoute } from '@tanstack/react-router'
import GenericProjectPage from '@/pages/project/_/[[...routeSlug]]'
export const Route = createFileRoute('/project/_/$')({
component: ProjectWildcardSplatRoute,
})
// Matches `/project/_/...`. TanStack exposes the trailing path as the
// `_splat` param; the underlying Next page normalises it back to the
// `routeSlug: string[]` shape it originally consumed.
//
// Naming delta: `project.[_].$.tsx` (path-as-filename) rather than
// `project/[_]/$.tsx` (nested directory) — see `org.[_].tsx` for the
// full rationale; in short, the directory + index.tsx form trips a
// router-generator bug that strips the escaped `_` segment when the
// last segment isn't escaped.
function ProjectWildcardSplatRoute() {
return <GenericProjectPage dehydratedState={undefined} />
}
+15
View File
@@ -0,0 +1,15 @@
import { createFileRoute } from '@tanstack/react-router'
import GenericProjectPage from '@/pages/project/_/[[...routeSlug]]'
export const Route = createFileRoute('/project/_')({
component: ProjectWildcardIndexRoute,
})
// Matches `/project/_` exactly. The companion splat route at
// `project.[_].$.tsx` handles `/project/_/...`. See `org.[_].tsx` for
// why this is named with the path-as-filename form rather than
// `project/[_]/index.tsx`.
function ProjectWildcardIndexRoute() {
return <GenericProjectPage dehydratedState={undefined} />
}