Files
2026-11-14 09:56:45 +00:00

8.9 KiB
Raw Permalink Blame History

Route names: Next.js app/ to TanStack Start src/routes/

The web shell moved from Next.js to TanStack Start route by route (M-wave M6). This table is the name each Next file took, so a change that still names a Next path finds its file here. The rules are F10 in docs/reference/app-kit.md; the worked example is the notes module through $org.$workspace.$project.$module.$.tsx and src/server/page.ts. A route file holds its server function and its view; what the loader does is in src/server/account-pages.ts (outside an organization) and src/server/org-pages.ts (under one), imported inside the handler.

Naming

  • Flat file names under src/routes/, one dot per path segment: $org._settings.members.$workspace.tsx is /<org>/members/<workspace>. No folders.
  • [param] becomes $param; [...rest] and [[...rest]] become $ (the splat, read as params._splat, empty at the parent's own path).
  • A Next layout.tsx becomes the route file of its own path ($org.tsx), rendering <Outlet />; the page.tsx beside it becomes <path>.index.tsx. A layout's checks move to its loader (or beforeLoad when a child needs the result as context), so they finish before the response starts.
  • A route.ts becomes a route file with server: { handlers: { GET, ... } } and no component.
  • A loading.tsx becomes pendingComponent: RoutePending on that path's route (src/ui/route-pending.tsx), which renders LoadingPage with the label from the root loader's ShellData.
  • error.tsx, global-error.tsx, not-found.tsx and forbidden.tsx are the root route's errorComponent and notFoundComponent (done, __root.tsx).
  • Static segments win over $param at the same depth, so $org._settings.members.tsx and $org.$workspace.tsx live side by side, as in Next.
  • src/routeTree.gen.ts is generated by the Start plugin on vite dev and vite build. It is not committed.
  • Organization pages share the frame in $org.tsx. A validated workspace is retained by src/ui/workspace-context.ts while Settings is open, so its sidebar and workspace settings use the workspace just visited even when the organization loader has not rerun. A full reload resolves the workspace preference on the server. In a workspace, the project picker and the open project’s sections follow the workspace destinations in the sidebar.
  • The organization home lists accessible workspaces with the kit's ResourceList. Projects are opened within their workspace. The workspace overview stays at /<org>/<workspace> and the dedicated directory is at /<org>/<workspace>/modules. The picker offers ?create=true there when the workspace grant allows project creation. A successful submission opens the new project overview at /<org>/<workspace>/<project>. Workspace and project breadcrumbs start at the workspace. Organization settings begin at the organization.
  • /settings/account opens Personal settings in the last workspace's organization when that organization is still among the signed-in person's memberships. With one organization it uses that one; with none or an ambiguous choice it stays on the standalone account page. A preference cookie never grants access.
  • Root 404, 403 and error states use the kit's PageState and keep the generic not-found response for inaccessible organizations; they do not display the standalone document header.
  • The multi-organization chooser at / uses the same centered AuthPage and AuthCard as onboarding. Its organization choices are links, and account, sign-out, language and theme controls remain available within the card; they use the shared kit and the shell's existing account and preference handling.

Page layout contract

  • List pages give their primary action to PageHeader or PageSection.actions. Role creation, workspace creation, the overview's project creation and document upload use the kit's FormPanel. The project directory also has a directly addressable create form. A panel form is mounted on demand in the same right panel as records, Agent and Help. A phone gets the shared full-screen sheet. The panel protects drafts and cannot close during a pending submission.
  • defineResource and ResourcePage remain the default for repeatable record CRUD (Notes is the worked example): one declaration owns columns, fields, mutations, record addresses and list refresh.
  • Keep settings forms inline when each section is saved independently. Keep short invitation forms with their one-time link result. Onboarding and identity stay on their dedicated entry layout; filter forms remain beside the list they filter.
  • Full permission matrices, document review and long-running work use full-page editors. A detail layout is appropriate for related record facts and context, rather than a permanent create form.
  • Optional modules remain disabled by default. Documents upload was verified separately with that module selected in an isolated checkout; this does not establish a live OCR/provider integration.

The table

Next file (apps/web/app/...) Start file (apps/web/src/routes/...) State
layout.tsx __root.tsx (loader shellData, shellComponent) done (M6a)
error.tsx, global-error.tsx __root.tsx errorComponent done (M6a)
not-found.tsx, forbidden.tsx __root.tsx notFoundComponent (data.forbidden is the 403 page) done (M6a)
globals.css src/ui/globals.css, linked by __root.tsx, then modules/styles.css done (M6a)
api/[[...rpc]]/route.ts, api/auth/[...all]/route.ts api.$.ts done (M6a)
auth/callback/route.ts, auth/dev-login/route.ts, auth/sign-in/start/route.ts, auth/sign-out/route.ts auth.$.ts done (M6a)
[org]/[workspace]/[project]/[module]/[[...rest]]/page.tsx $org.$workspace.$project.$module.$.tsx done (M6a, notes)
page.tsx index.tsx done (M6b)
onboarding/page.tsx onboarding.tsx done (M6b)
members/page.tsx members.tsx done (M6b)
settings/account/page.tsx settings.account.tsx done (M6b)
invitations/[org]/[token]/page.tsx invitations.$org.$token.tsx done (M6b)
auth/sign-in/page.tsx auth.sign-in.tsx done (M6b)
auth/sign-up/page.tsx auth.sign-up.tsx done (M6b)
auth/signed-out/page.tsx auth.signed-out.tsx done (M6b)
api/brand/og/route.ts api.brand.og.ts done (M6b)
[org]/layout.tsx $org.tsx done (M6c)
[org]/page.tsx $org.index.tsx done (M6c)
[org]/brand/logo/[variant]/route.ts $org.brand.logo.$variant.ts done (M6c)
[org]/brand/og/route.ts $org.brand.og.ts done (M6c)
[org]/branding/page.tsx $org._settings.branding.tsx done (M6c)
[org]/settings/page.tsx $org._settings.settings.index.tsx done (M6c)
[org]/workspaces/page.tsx $org._settings.workspaces.tsx done (M6c)
[org]/access-audit/{layout,loading,page}.tsx $org._settings.access-audit.tsx done (M6c)
[org]/audit/{layout,loading,page}.tsx $org._settings.audit.tsx done (M6c); a tab of Settings since 2026-11-03
[org]/invitations/{layout,loading,page}.tsx $org._settings.invitations.tsx done (M6c)
[org]/members/layout.tsx, loading.tsx $org._settings.members.tsx done (M6c)
[org]/members/page.tsx $org._settings.members.index.tsx done (M6c)
[org]/members/[workspace]/page.tsx $org._settings.members.$workspace.index.tsx done (M6c)
[org]/members/[workspace]/[project]/page.tsx $org._settings.members.$workspace.$project.tsx done (M6c)
[org]/roles/layout.tsx, loading.tsx $org._settings.roles.tsx done (M6c)
[org]/roles/page.tsx $org._settings.roles.index.tsx done (M6c)
[org]/roles/[role]/page.tsx $org._settings.roles.$role.tsx done (M6c)
[org]/[workspace]/layout.tsx $org.$workspace.tsx done (M6c)
[org]/[workspace]/page.tsx $org.$workspace.index.tsx done (M6c)
[org]/[workspace]/[project]/layout.tsx $org.$workspace.$project.tsx done (M6c)
[org]/[workspace]/[project]/page.tsx $org.$workspace.$project.index.tsx done (M6c)
[org]/[workspace]/[project]/documents/page.tsx $org.$workspace.$project.documents.index.tsx done (M6c)
[org]/[workspace]/[project]/documents/[fileId]/page.tsx $org.$workspace.$project.documents.$fileId.tsx done (M6c)

A module's own pages (modules/<id>/ui/pages.tsx) need no row: each joins modules/pages.ts when its page is a ProjectPage of F10 (load and View), as notes is. A module's page above a project is a WorkspacePage in workspacePages of the same file, served by one route with no Next ancestor: $org.$workspace.modules.$module.$.tsx, /<org>/<workspace>/modules/<module>/.... modules is reserved from project slugs (RESERVED_PROJECT_SLUGS), as a bare /<org>/<workspace>/<module> would be a project's slug.