Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.refactkit.com/llms.txt

Use this file to discover all available pages before exploring further.

RefactKit uses TanStack Router’s file-based routing system to map the filesystem directly to your URL structure. Every file you create inside src/routes/ becomes a route — no manual registration required. The _app/organizations/$slug/ subtree is where all organization-scoped pages live, and understanding its conventions will let you ship new workspace features quickly and correctly.

Route structure overview

The src/routes/ directory is organized into three logical zones:
DirectoryPurpose
_auth/Public routes: login, signup, password reset
_app/Authenticated shell: sidebar layout, settings
_app/organizations/$slug/Organization workspace pages
The leading _ prefix makes _app and _auth pathless layout routes — they wrap their children in a layout component without adding a URL segment.

Create a page inside an organization workspace

Every organization page lives under src/routes/_app/organizations/$slug/. The $slug segment is a dynamic parameter that TanStack Router populates automatically from the URL.
1

Create the route file

Add a new file at src/routes/_app/organizations/$slug/my-page.tsx. The filename becomes the URL segment — this file will be served at /organizations/acme/my-page.
2

Define the route export

Every route file must export a Route constant created with createFileRoute. Pass the full route path as a type-safe string literal:
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/_app/organizations/$slug/my-page')({
  component: MyPage,
  loader: async ({ context }) => {
    // Seed the TanStack Query cache for SSR
    await context.queryClient.ensureQueryData(myQueryOptions(context.params.slug))
  },
})

function MyPage() {
  return <div>My new page</div>
}
3

Read loader data in your component

Call Route.useLoaderData() inside the component to access whatever the loader returned — fully type-safe, no prop drilling needed.
function MyPage() {
  const { slug } = Route.useParams()
  // or access parent loader data:
  const { org, role } = OrgRoute.useLoaderData()
  return <h1>Welcome to {org.name}</h1>
}

Read parent loader data

The $slug layout route at src/routes/_app/organizations/$slug/route.tsx loads the organization and the current user’s role before any child route renders. Import its Route export to access that data:
import { Route as OrgRoute } from './route'

function MyPage() {
  const { org, role } = OrgRoute.useLoaderData()
  // org.id, org.name, org.slug, org.logo are all available here
}
The layout loader verifies the organization exists and redirects to /organizations if it does not:
// src/routes/_app/organizations/$slug/route.tsx
export const Route = createFileRoute('/_app/organizations/$slug')({
  loader: async ({ params, context }) => {
    const { org, role } = await context.queryClient.ensureQueryData(
      orgBySlugQuery(params.slug)
    )
    if (!org) throw redirect({ to: '/organizations' })
    return { org, role }
  },
  component: () => <Outlet />,
})

Protect routes with beforeLoad

Use beforeLoad to verify session state or enforce role requirements before any rendering occurs. Throwing a redirect from beforeLoad prevents the loader and component from ever running.
import { createFileRoute, redirect } from '@tanstack/react-router'

export const Route = createFileRoute('/_app/organizations/$slug/settings')({
  beforeLoad: async ({ context }) => {
    const { session } = await getServerSession()
    if (!session) throw redirect({ to: '/login' })
  },
  component: OrgSettingsPage,
})
For role-based access, you can read the parent loader data inside beforeLoad and redirect non-owners:
beforeLoad: ({ context }) => {
  const role = context.queryClient.getQueryData(orgBySlugQuery(slug))?.role
  if (role !== 'owner') throw redirect({ to: '/organizations/$slug/dashboard' })
}
The _app root layout already verifies the session in its own loader and redirects to /login if there is none. Child routes benefit from this automatically — but you should still use beforeLoad for role-specific restrictions.

SSR best practices: seed the cache with ensureQueryData

RefactKit renders pages on the server before sending them to the browser. To avoid a second network round-trip during hydration, seed the TanStack Query cache inside your route loader using ensureQueryData:
export const Route = createFileRoute('/_app/organizations/$slug/my-page')({
  loader: async ({ context, params }) => {
    await context.queryClient.ensureQueryData(myQueryOptions(params.slug))
  },
  component: MyPage,
})
ensureQueryData only fetches if the data is not already in the cache — it is safe to call multiple times. Once the page hydrates in the browser, useQuery picks up the pre-filled cache and renders instantly without a loading state.
Never call fetch() against your own API routes during SSR. Use server functions (createServerFn) or direct database queries instead. Calling your own HTTP endpoints over the network during server rendering creates unnecessary latency and can cause circular request issues under Nitro.

Search parameter validation

If your page needs query string parameters, validate them with a validateSearch function so TanStack Router infers their types:
export const Route = createFileRoute('/_app/organizations/$slug/gallery')({
  validateSearch: (search: Record<string, unknown>) => ({
    page: Number(search?.page ?? 1),
  }),
  component: GalleryPage,
})

function GalleryPage() {
  const { page } = Route.useSearch()
  // page is typed as number
}

Reactivity when switching organizations

When a user switches between organizations in the sidebar, the $slug param changes and the loaders re-run automatically. To prevent stale local state from leaking across organizations, key your page container on the organization ID:
function MyPage() {
  const { org } = OrgRoute.useLoaderData()
  return (
    <div key={org.id}>
      {/* All state inside resets when org.id changes */}
    </div>
  )
}