Skip to content

Adding a Page

This guide walks through adding a new page that loads data from the server. The example adds a /notifications page to the authenticated app.

Add a server function in functions/ that fetches data with appropriate middleware:

src/functions/notifications.ts
import { createServerFn } from '@tanstack/react-start'
import { authMiddleware } from '#/middleware/auth'
import { getNotifications } from '#/services/notification.service'
export const listNotifications = createServerFn({ method: 'GET' })
.middleware([authMiddleware])
.handler(async ({ context }) => {
return getNotifications(context.user.id)
})

Add the route file. The loader awaits the server function and returns the data:

src/routes/_app/notifications.tsx
import { createFileRoute } from '@tanstack/react-router'
import { listNotifications } from '#/functions/notifications'
import { NotificationList } from '#/components/notifications/notification-list'
import { PageHeader } from '#/components/shared/page-header'
export const Route = createFileRoute('/_app/notifications')({
loader: () => listNotifications(),
component: NotificationsPage,
})
function NotificationsPage() {
return (
<>
<PageHeader title="Notifications" />
<NotificationList />
</>
)
}

The component reads loader data via getRouteApi(...).useLoaderData():

src/components/notifications/notification-list.tsx
import { getRouteApi } from '@tanstack/react-router'
const routeApi = getRouteApi('/_app/notifications')
export function NotificationList() {
const notifications = routeApi.useLoaderData()
return (
<ul>
{notifications.map((n) => (
<li key={n.id}>{n.message}</li>
))}
</ul>
)
}

If the data is read directly inside the route’s component (no separate file), use Route.useLoaderData() instead.

After mutating notifications, call await router.invalidate() to refresh the loader:

async function markAllRead() {
await markNotificationsRead()
await router.invalidate()
}

The pattern is always the same:

  1. Service - Business logic and database query
  2. Server function - RPC boundary with middleware
  3. Route - Loader awaits the server function and returns its data
  4. Component - Reads data via Route.useLoaderData() (or getRouteApi(...).useLoaderData() from a child file)